How to integrate Web3Auth tKey MPC SDK
This guide will help you make a react application using Web3Auth tKey MPC SDK, covering the basic functionality on how to use it.
Live Demo: https://w3a.link/tkey-mpc-example
This is an advanced guide, meant for enterprise developers and contains multiple moving parts that might be difficult to understand. Please contact our team if you're looking to integrate this SDK, and we'll be happy to help you out.
For general integrations, MPC Core Kit is the best alternative to be used.
Prerequisites
- A basic knowledge of JavaScript and React.
- Ideal to have a knowledge about service workers in React.
- A Google Developer account to be used as Login provider for Web3Auth Custom Authentication.
- Create a Web3Auth account on the Web3Auth Dashboard
Understanding the Web3Auth tKey JS MPC SDK
With the Web3Auth infrastructure, your key is divided into multiple parts and stored across your devices and our Auth Network. This ensures that your key is always available and never stored in a single place. While in the traditional Web3Auth SDK, your key was dynamically reconstructed in the frontend using shamir secret sharing. With the new Web3Auth MPC (Multi Party Computation) architecture, it is never reconstructed. Instead, these partial keys are stored across different locations, and your device is used to make partial signatures for your message/ transaction. These are finally returned to the frontend where using TSS (Threshold Signature Scheme), these signatures are combined to make a final signature. You can use this finally signed message/transaction to make a transaction on the blockchain.
The Threshold Signature Scheme (TSS) is a cryptographic primitive for distributed key generation and signing. The use of TSS in Web3Auth's Auth network is a new paradigm that can provide numerous benefits, especially in terms of security.
As you can notice in this diagram above, the final output, i.e., the User's TSS Account, is generated in multiple stages within the infrastructure. Since this is a TSS- MPC based infrastructure, you don't get back a private key, but signatures that can be used to make transactions on the blockchain. Let's understand each of these stages in detail.
Factors
Social Login Factor
This is the primary way for a user to access their account. This step involves authentication using a user's preferred social login provider. The idToken received from the social login provider here is passed to the Web3Auth Auth Network to generate the TSS Shares in the Nodes. By default, these nodes have a threshold of 3/5 that can be customized according to requirements. When a user logs in, the Auth Network generates signatures corresponding to the TSS Shares in the nodes and returns them to the user's end. These signatures are then used alongside other shares to generate the final TSS Account signatures.
Device Factor
This is the second factor used to access the user's account. This step involves the generation of a TSS Share on the user's device and using that to generate a final signature for the TSS Account alongside the social login factor. This ensures the user logs in using their trusted device and maintains a proper non-custodial setup.
Backup Factor
A user has a choice to generate as many backup factors as needed to access their account. This step involves the generation of a TSS Share on the user's end and storing them in whichever location they prefer. This share can be used similarly to the device share to generate a final signature for the TSS Account alongside the social login and/or device factors.
Threshold
The threshold is the minimum number of shares required to generate a final signature for the TSS Account. This threshold, by default, is set to 3/5 on the Auth Network and 2/3 for the user's device front. This ensures high availability and ease of access on both ends alongside optimum security. Both these thresholds can be customized according to the requirements.
Components of a Factor
TSS Shares
The TSS Shares are the main component needed for the generation of the final working signature of the user. These shares are generated using distributed key generation and are stored in the Auth Network and the user's device. Since these shares are generated using MPC, they are never reconstructed and always stay decentralized and secure.
Metadata Key & Shares
The Metadata Key closely mimics the storage of the TSS Key and Shares. The only difference is that the metadata key is always reconstructed and used for its encryption/decryption capabilities. It utilizes basic Shamir's Secret Sharing and is initially generated on a users' frontend. The metadata key ensures that the infrastructure is backward compatible with the existing Web3Auth architecture, which utilizes SSS (Shamir's Secret Sharing) as the main key reconstruction scheme.
Factor Keys
To enable refresh, deletion, and rotational capabilities on the tssKey
, we also introduce
factorKeys. Factor Keys are randomly generated keys unique to each factor-generated user's
device and backups, like users' phones, chrome extension, on their cloud, assisting third parties,
etc.
As shares to the TSS Key and/or Metadata Key may rotate, Factor Keys allow a consistent secret to being saved in these different locations.
For this guide, we will be talking through the Web3Auth tKey JS MPC SDK and showing you a basic
example of using it with @tkey-mpc/default
package.
Setup
Setup your Google App
-
Follow Google’s instructions to set up an OAuth 2.0 app.
-
Add your application's redirect URI into the "Authorized redirect URIs" field. This is the URL that Google will redirect to after authentication.
http://localhost:3000/serviceworker/redirect
-
Obtain the OAuth
Client ID
from your App on the Google Developer dashboard
Setup your Web3Auth Dashboard
-
Create a Verifier from the Custom Auth Section of the Web3Auth Developer Dashboard with following configuration:
- Choose a name of your choice for the verifier identifier.
eg. google-tkey-w3a
- Select environment: Please note the verifier will be deployed on
sapphire_mainnet
orsapphire_devnet
environment. - Select
Google
from the Login Provider. - Paste the Client ID from the Google App(above) to the
Client ID
field. - Click on
Create
button to create your verifier. It may take up to 10-20 minutes to deploy verifier on testnet. You'll receive an email once it's complete.
- Choose a name of your choice for the verifier identifier.
-
You will require the
verifierName
of the newly created verifier.
Setting up your React Project
We will need to use service workers while implementing the tKey SDK to handle the redirect login flow. This can be done by using a progressive react application.
For a new project, get started with the following command:
npx create-react-app tkey-mpc-demo --template cra-template-pwa-typescript
cd tkey-mpc-demo
For an existing project, add a service worker.
Setting up the service worker
Further, we need to setup the service worker according to our needs of the project, i.e handling the redirect login flow. Service worker basically sits between the frontend application, browser and the network. For the simplicity of this guide, we have added a boilerplate code. The easiest way to do that is as follows
mkdir public/serviceworker
wget https://github.com/Web3Auth/web3auth-core-kit-examples/blob/main/tkey/tkey-react-popup-example/public/serviceworker/sw.js -O public/serviceworker/sw.js
For polyfill issues and BigInt issue, please checkout the troubleshooting page.
For this guide, we'll be using a React application to demonstrate how the Web3Auth MPC SDK works. You can use any other framework of your choice using any other Web3Auth Guides / Examples. Just modify the functions to make it work.
Installation
We will be starting with a 2/2 flow, i.e Factor1: Social Factor
and Factor2: Device Factor
.
Please note that this terminology is different from our conventional terminology of Shares A and B
since we will not be constructing the private key in the frontend. Instead, we will be using the
these Factors stored in the different locations to make a final signature.
For this project, we will be using the @tkey-mpc/default
package alongside multiple helper
packages, that contain all the needed functionalities of the tKey MPC SDK. We will be using the
following packages highlighted in the package.json file:
{
...
"dependencies": {
"@ethereumjs/common": "^3.1.1",
"@ethereumjs/tx": "^4.1.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@tkey-mpc/common-types": "^8.1.0",
"@tkey-mpc/core": "^8.1.0",
"@tkey-mpc/service-provider-torus": "^8.1.0",
"@tkey-mpc/share-serialization": "^8.1.0",
"@tkey-mpc/storage-layer-torus": "^8.1.0",
"@toruslabs/constants": "^10.0.0",
"@toruslabs/eslint-config-typescript": "^2.0.0",
"@toruslabs/fnd-base": "^12.0.0",
"@toruslabs/torus.js": "^10.0.5",
"@toruslabs/tss-client": "^1.6.1-alpha.0",
"@toruslabs/tss-lib": "^1.6.0-alpha.0",
"@web3auth-mpc/ethereum-provider": "^2.1.22",
"@web3auth/base": "^6.1.7",
"@web3auth/ethereum-provider": "^6.1.7",
"bn.js": "^5.2.1",
"copy-webpack-plugin": "^11.0.0",
"eccrypto": "^1.1.6",
"html-webpack-plugin": "^5.5.0",
"jsrsasign": "^10.6.1",
"keccak256": "^1.0.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"sweetalert": "^2.1.2",
"typescript": "^4.9.3",
"web-vitals": "^2.1.4",
"web3": "^1.8.2"
},
"devDependencies": {
"@types/eccrypto": "^1.1.3",
"@types/elliptic": "^6.4.14",
"@types/jsrsasign": "^10.5.5",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.45",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"assert": "^2.0.0",
"buffer": "^6.0.3",
"browserify-zlib": "^0.2.0",
"crypto-browserify": "^3.12.0",
"https-browserify": "^1.0.0",
"os-browserify": "^0.3.0",
"process": "^0.11.10",
"react-app-rewired": "^2.2.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"url": "^0.11.0",
"workbox-background-sync": "^6.5.4",
"workbox-broadcast-update": "^6.5.4",
"workbox-cacheable-response": "^6.5.4",
"workbox-core": "^6.5.4",
"workbox-expiration": "^6.5.4",
"workbox-google-analytics": "^6.5.4",
"workbox-navigation-preload": "^6.5.4",
"workbox-precaching": "^6.5.4",
"workbox-range-requests": "^6.5.4",
"workbox-routing": "^6.5.4",
"workbox-strategies": "^6.5.4",
"workbox-streams": "^6.5.4"
},
...
}
These packages are quite similar to our current tKey JS SDK, however contain some additional parameters and functions needed for MPC. You can also refer to our tKey JS SDK Reference for more details on the conventional functions involved.
Understanding the Dependencies
@tkey-mpc/*
Contains all the functionalities of tKey needed for using MPC. These packages help us create the device factor and login to the Web3Auth.
@toruslabs/*
Contains all the helper packages developed by Web3Auth/ Torus Labs for MPC implmentation.
bn.js
, eccrypto
, jsrsasign
, keccak256
These packages help in key generation and signatures.
@web3auth-mpc/ethereum-provider
, web3
, @ethereumjs/*
These packages help in connecting to the Ethereum network and creating transactions.
Initialization
After Installation, the next step to use Web3Auth Core Kit tKey SDK is to Initialize the SDK.
import ThresholdKey from "@tkey-mpc/core";
import { TorusServiceProvider } from "@tkey-mpc/service-provider-torus";
import { TorusStorageLayer } from "@tkey-mpc/storage-layer-torus";
import { ShareSerializationModule } from "@tkey-mpc/share-serialization";
// Configuration of Service Provider
const torusSp = new TorusServiceProvider({
useTSS: true,
customAuthArgs: {
network: "sapphire_devnet",
web3AuthClientId: "YOUR_CLIENT_ID", // anything will work on localhost, but get one valid clientID before hosting, from https://dashboard.web3auth.io
baseUrl: `${window.location.origin}/serviceworker`,
enableLogging: true,
},
});
// Configuration of Metadata Storage Layer
const storageLayer = new TorusStorageLayer({
hostUrl: "https://sapphire-dev-2-1.authnetwork.dev/metadata",
enableLogging: true,
});
// Configuration of Share Serialization Module
const shareSerializationModule = new ShareSerializationModule();
// Instantiation of tKey
export const tKey = new ThresholdKey({
enableLogging: true,
serviceProvider: torusSp as any,
storageLayer: storageLayer as any,
manualSync: true,
modules: {
shareSerialization: shareSerializationModule,
},
});
useEffect(() => {
const init = async () => {
// Initialization of Service Provider
try {
await (tKey.serviceProvider as any).init();
} catch (error) {
console.error(error);
}
};
init();
}, []);
Adding the MPC utils file
We have created an utils.ts
file to help us with the MPC functionalities. You can directly copy
this file from our
GitHub Example
or the code below.
src/utils.ts
import BN from "bn.js";
import { encrypt, getPubKeyECC, Point, randomSelection, ShareStore } from "@tkey-mpc/common-types";
import EC from "elliptic";
import { generatePrivate } from "@toruslabs/eccrypto";
import { Client } from "@toruslabs/tss-client";
import * as tss from "@toruslabs/tss-lib";
import { EthereumSigningProvider } from "@web3auth-mpc/ethereum-provider";
import keccak256 from "keccak256";
import Web3 from "web3";
import type { provider } from "web3-core";
import { utils } from "@toruslabs/tss-client";
const { getDKLSCoeff, setupSockets } = utils;
const parties = 4;
const clientIndex = parties - 1;
const tssImportUrl = `https://sapphire-dev-2-2.authnetwork.dev/tss/v1/clientWasm`;
const DELIMITERS = {
Delimiter1: "\u001c",
Delimiter2: "\u0015",
Delimiter3: "\u0016",
Delimiter4: "\u0017",
};
export function getEcCrypto(): any {
// eslint-disable-next-line new-cap
return new EC.ec("secp256k1");
}
const ec = getEcCrypto();
export const generateTSSEndpoints = (tssNodeEndpoints: string[], parties: number, clientIndex: number) => {
const endpoints: string[] = [];
const tssWSEndpoints: string[] = [];
const partyIndexes: number[] = [];
for (let i = 0; i < parties; i++) {
partyIndexes.push(i);
if (i === clientIndex) {
endpoints.push(null as any);
tssWSEndpoints.push(null as any);
} else {
endpoints.push(tssNodeEndpoints[i]);
tssWSEndpoints.push(new URL(tssNodeEndpoints[i]).origin);
}
}
return { endpoints, tssWSEndpoints, partyIndexes };
};
export const setupWeb3 = async (chainConfig: any, loginReponse: any, signingParams: any) => {
try {
const ethereumSigningProvider = new EthereumSigningProvider({
config: {
chainConfig,
},
});
const { tssNonce, tssShare2, tssShare2Index, compressedTSSPubKey, signatures, nodeDetails } = signingParams;
const { verifier, verifierId } = loginReponse.userInfo;
const vid = `${verifier}${DELIMITERS.Delimiter1}${verifierId}`;
const sessionId = `${vid}${DELIMITERS.Delimiter2}default${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}`;
/*
pass user's private key here.
after calling setupProvider, we can use
*/
const sign = async (msgHash: Buffer) => {
const randomSessionNonce = keccak256(generatePrivate().toString("hex") + Date.now());
// session is needed for authentication to the web3auth infrastructure holding the factor 1
const currentSession = `${sessionId}${randomSessionNonce.toString("hex")}`;
// 1. setup
// generate endpoints for servers
const { endpoints, tssWSEndpoints, partyIndexes } = generateTSSEndpoints(nodeDetails.serverEndpoints, parties, clientIndex);
// setup mock shares, sockets and tss wasm files.
const [sockets] = await Promise.all([setupSockets(tssWSEndpoints as string[], randomSessionNonce.toString("hex")), tss.default(tssImportUrl)]);
const participatingServerDKGIndexes = [1, 2, 3];
const dklsCoeff = getDKLSCoeff(true, participatingServerDKGIndexes, tssShare2Index);
const denormalisedShare = dklsCoeff.mul(tssShare2).umod(ec.curve.n);
const share = Buffer.from(denormalisedShare.toString(16, 64), "hex").toString("base64");
if (!currentSession) {
throw new Error(`sessionAuth does not exist ${currentSession}`);
}
if (!signatures) {
throw new Error(`Signature does not exist ${signatures}`);
}
const client = new Client(
currentSession,
clientIndex,
partyIndexes,
endpoints,
sockets,
share,
compressedTSSPubKey.toString("base64"),
true,
tssImportUrl,
);
const serverCoeffs: any = {};
for (let i = 0; i < participatingServerDKGIndexes.length; i++) {
const serverIndex = participatingServerDKGIndexes[i];
serverCoeffs[serverIndex] = getDKLSCoeff(false, participatingServerDKGIndexes, tssShare2Index, serverIndex).toString("hex");
}
client.precompute(tss, { signatures, server_coeffs: serverCoeffs });
await client.ready();
const { r, s, recoveryParam } = await client.sign(tss as any, Buffer.from(msgHash).toString("base64"), true, "", "keccak256", {
signatures,
});
await client.cleanup(tss, { signatures, server_coeffs: serverCoeffs });
return { v: recoveryParam, r: Buffer.from(r.toString("hex"), "hex"), s: Buffer.from(s.toString("hex"), "hex") };
};
if (!compressedTSSPubKey) {
throw new Error(`compressedTSSPubKey does not exist ${compressedTSSPubKey}`);
}
const getPublic: () => Promise<Buffer> = async () => {
return compressedTSSPubKey;
};
await ethereumSigningProvider.setupProvider({ sign, getPublic });
console.log(ethereumSigningProvider.provider);
const web3 = new Web3(ethereumSigningProvider.provider as provider);
return web3;
} catch (e) {
console.error(e);
return null;
}
};
export type FactorKeyCloudMetadata = {
deviceShare: ShareStore;
tssShare: BN;
tssIndex: number;
};
const fetchDeviceShareFromTkey = async (tKey: any) => {
if (!tKey) {
console.error("tKey not initialized yet");
return;
}
try {
const polyId = tKey.metadata.getLatestPublicPolynomial().getPolynomialID();
const shares = tKey.shares[polyId];
let deviceShare: ShareStore | null = null;
for (const shareIndex in shares) {
if (shareIndex !== "1") {
deviceShare = shares[shareIndex];
}
}
return deviceShare;
} catch (err: any) {
console.error({ err });
throw new Error(err);
}
};
export const addFactorKeyMetadata = async (tKey: any, factorKey: BN, tssShare: BN, tssIndex: number, factorKeyDescription: string) => {
if (!tKey) {
console.error("tKey not initialized yet");
return;
}
const { requiredShares } = tKey.getKeyDetails();
if (requiredShares > 0) {
console.error("not enough shares for metadata key");
}
const metadataDeviceShare = await fetchDeviceShareFromTkey(tKey);
const factorIndex = getPubKeyECC(factorKey).toString("hex");
const metadataToSet: FactorKeyCloudMetadata = {
deviceShare: metadataDeviceShare as ShareStore,
tssShare,
tssIndex,
};
// Set metadata for factor key backup
await tKey.addLocalMetadataTransitions({
input: [{ message: JSON.stringify(metadataToSet) }],
privKey: [factorKey],
});
// also set a description on tkey
const params = {
module: factorKeyDescription,
dateAdded: Date.now(),
tssShareIndex: tssIndex,
};
await tKey.addShareDescription(factorIndex, JSON.stringify(params), true);
};
export const copyExistingTSSShareForNewFactor = async (tKey: any, newFactorPub: Point, factorKeyForExistingTSSShare: BN) => {
if (!tKey) {
throw new Error("tkey does not exist, cannot copy factor pub");
}
if (!tKey.metadata.factorPubs || !Array.isArray(tKey.metadata.factorPubs[tKey.tssTag])) {
throw new Error("factorPubs does not exist, failed in copy factor pub");
}
if (!tKey.metadata.factorEncs || typeof tKey.metadata.factorEncs[tKey.tssTag] !== "object") {
throw new Error("factorEncs does not exist, failed in copy factor pub");
}
const existingFactorPubs = tKey.metadata.factorPubs[tKey.tssTag].slice();
const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]);
const { tssShare, tssIndex } = await tKey.getTSSShare(factorKeyForExistingTSSShare);
const factorEncs = JSON.parse(JSON.stringify(tKey.metadata.factorEncs[tKey.tssTag]));
const factorPubID = newFactorPub.x.toString(16, 64);
factorEncs[factorPubID] = {
tssIndex,
type: "direct",
userEnc: await encrypt(
Buffer.concat([
Buffer.from("04", "hex"),
Buffer.from(newFactorPub.x.toString(16, 64), "hex"),
Buffer.from(newFactorPub.y.toString(16, 64), "hex"),
]),
Buffer.from(tssShare.toString(16, 64), "hex"),
),
serverEncs: [],
};
tKey.metadata.addTSSData({
tssTag: tKey.tssTag,
factorPubs: updatedFactorPubs,
factorEncs,
});
};
export const addNewTSSShareAndFactor = async (
tKey: any,
newFactorPub: Point,
newFactorTSSIndex: number,
factorKeyForExistingTSSShare: BN,
signatures: any,
) => {
try {
if (!tKey) {
throw new Error("tkey does not exist, cannot add factor pub");
}
if (!(newFactorTSSIndex === 2 || newFactorTSSIndex === 3)) {
throw new Error("tssIndex must be 2 or 3");
}
if (!tKey.metadata.factorPubs || !Array.isArray(tKey.metadata.factorPubs[tKey.tssTag])) {
throw new Error("factorPubs does not exist");
}
const existingFactorPubs = tKey.metadata.factorPubs[tKey.tssTag].slice();
const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]);
const existingTSSIndexes = existingFactorPubs.map((fb: any) => tKey.getFactorEncs(fb).tssIndex);
const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]);
const { tssShare, tssIndex } = await tKey.getTSSShare(factorKeyForExistingTSSShare);
tKey.metadata.addTSSData({
tssTag: tKey.tssTag,
factorPubs: updatedFactorPubs,
});
const rssNodeDetails = await tKey._getRssNodeDetails();
const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails;
const randomSelectedServers = randomSelection(
new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1),
Math.ceil(rssNodeDetails.serverEndpoints.length / 2),
);
const verifierNameVerifierId = tKey.serviceProvider.getVerifierNameVerifierId();
await tKey._refreshTSSShares(true, tssShare, tssIndex, updatedFactorPubs, updatedTSSIndexes, verifierNameVerifierId, {
selectedServers: randomSelectedServers,
serverEndpoints,
serverPubKeys,
serverThreshold,
authSignatures: signatures,
});
} catch (err) {
console.error(err);
throw err;
}
};
Triggering Login using Service Provider
Once you have initialized the Web3Auth Service Provider, you're ready to trigger the login process.
This is a needed step since this will generate a private key which will be needed by the tKey to
generate it's share. This is done by calling the triggerLogin()
function within the tKey
instance's of serviceProvider
property.
const triggerLogin = async () => {
if (!tKey) {
uiConsole("tKey not initialized yet");
return;
}
try {
// Triggering Login using Service Provider ==> opens the popup
const loginResponse = await (tKey.serviceProvider as any).triggerLogin({
typeOfLogin: "google",
verifier: "google-tkey-w3a",
clientId: "774338308167-q463s7kpvja16l4l0kko3nb925ikds2p.apps.googleusercontent.com",
});
console.log("loginResponse", loginResponse);
setLoginResponse(loginResponse);
setUser(loginResponse.userInfo);
return loginResponse;
} catch (error) {
uiConsole(error);
}
};
Initialize tKey
Once you have triggered the login process, you're ready to initialize the tKey. This will generate you a Metadata Key corresponding to your login provider, which helps the SDK detect the login of the user. Additionally. we'll generate the device (factor2) factor and store/retrieve it from the local storage helping us to setup the 2/2 basic flow to make transactions.
Check if user is new or existing
The Web3Auth Metadata stores the information about the user's key locations, like how many keys are
generated, which factorPub represents which key according to the set description etc. You can
customise what information you want to store in the metadata. The utils file we created earlier has
a function called addFactorKeyMetadata
which is used to add data in the metadata, you can
customise that according to your needs.
By default, if the key is not present, the metadata returns a KEY_NOT_FOUND
message. We can use
this to check if the user is new or existing.
const isMetadataPresent = async (privateKeyBN: BN) => {
const metadata = await tKey.storageLayer.getMetadata({ privKey: privateKeyBN });
if (
metadata &&
Object.keys(metadata).length > 0 &&
(metadata as any).message !== "KEY_NOT_FOUND"
) {
return true;
} else {
return false;
}
};
Store tKey device factor in local storage
Since we are use the tKey Core MPC SDK, we need to implement the device storage functions ourselves. We'll be using the local storage to store the device share for this example.
import BN from "bn.js";
const [localFactorKey, setLocalFactorKey] = useState<BN | null>(null);
useEffect(() => {
if (!localFactorKey) return;
localStorage.setItem(
`tKeyLocalStore\u001c${loginResponse.userInfo.verifier}\u001c${loginResponse.userInfo.verifierId}`,
JSON.stringify({
factorKey: localFactorKey.toString("hex"),
verifier: loginResponse.userInfo.verifier,
verifierId: loginResponse.userInfo.verifierId,
}),
);
}, [localFactorKey]);
Implementing the main initializeNewKey
function
import { addFactorKeyMetadata, getTSSPubKey } from "./utils";
import BN from "bn.js";
import { generatePrivate } from "eccrypto";
const [metadataKey, setMetadataKey] = useState<any>();
const [localFactorKey, setLocalFactorKey] = useState<BN | null>(null);
const [oAuthShare, setOAuthShare] = useState<any>(null);
const [signingParams, setSigningParams] = useState<any>(null);
const initializeNewKey = async () => {
if (!tKey) {
uiConsole("tKey not initialized yet");
return;
}
try {
// Trigger Login using Service Provider
const loginResponse = await triggerLogin();
const OAuthShare = new BN(TorusUtils.getPostboxKey(loginResponse), "hex");
setOAuthShare(OAuthShare); // This private key is the OAuth Metadata Share
// Filter out the null values from the signatures
const signatures = loginResponse.sessionData.sessionTokenData
.filter((i) => Boolean(i))
.map((session) => JSON.stringify({ data: session.token, sig: session.signature }));
// Get the Device Share
const tKeyLocalStoreString = localStorage.getItem(
`tKeyLocalStore\u001c${loginResponse.userInfo.verifier}\u001c${loginResponse.userInfo.verifierId}`,
);
const tKeyLocalStore = JSON.parse(tKeyLocalStoreString || "{}");
// Define the factorKey variable for later usage. Please note that this factorKey represents the factorKey 2, ie. device share or recovery share in the 2/2 flow
let factorKey: BN | null = null;
// Check if the user is new or existing
const existingUser = await isMetadataPresent(OAuthShare);
if (!existingUser) {
// If the user is new, we'll generate a new factorKey & deviceShare and store it in the local storage. The factorKey can be used to get the deviceShare from metadata. Device Share is stored on the metadata encrypted by the factorKey.
factorKey = new BN(generatePrivate());
const deviceTSSShare = new BN(generatePrivate());
const deviceTSSIndex = 2;
const factorPub = getPubKeyPoint(factorKey);
await tKey.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex });
} else {
if (
tKeyLocalStore.verifier === loginResponse.userInfo.verifier &&
tKeyLocalStore.verifierId === loginResponse.userInfo.verifierId
) {
// If the user is existing and the local storage has the factorKey corresponding the logged in user, we'll use that factorKey to get the deviceShare from metadata. Device Share is stored on the metadata encrypted by the factorKey.
factorKey = new BN(tKeyLocalStore.factorKey, "hex");
} else {
// If the user is existing but the local storage doesn't have the factorKey corresponding the logged in user, we'll ask the user to enter the backup factor key to get the deviceShare from metadata.
try {
factorKey = await swal("Enter your backup share", {
content: "input" as any,
}).then(async (value) => {
uiConsole(value);
return await (tKey.modules.shareSerialization as any).deserialize(value, "mnemonic");
});
} catch (error) {
uiConsole(error);
throw new Error("Invalid backup share");
}
}
// if the factorKey is null, it means the user entered an invalid backup share
if (factorKey === null) throw new Error("Backup share not found");
// Get the corresponding TSS share from metadata using the factorKey
const factorKeyMetadata = await tKey.storageLayer.getMetadata<{
message: string;
}>({
privKey: factorKey,
});
// If the metadata doesn't have the TSS Share, it means the user has entered an invalid backup share
if (factorKeyMetadata.message === "KEY_NOT_FOUND") {
throw new Error("no metadata for your factor key, reset your account");
}
const metadataShare = JSON.parse(factorKeyMetadata.message);
if (!metadataShare.deviceShare || !metadataShare.tssShare)
throw new Error("Invalid data from metadata");
const metadataDeviceShare = metadataShare.deviceShare;
// Initialize the tKey with the TSS Share from metadata
await tKey.initialize({ neverInitializeNewKey: true });
await tKey.inputShareStoreSafe(metadataDeviceShare, true);
}
// Checks the requiredShares to reconstruct the tKey, starts from 2 by default and each of the above share reduce it by one.
const { requiredShares } = tKey.getKeyDetails();
if (requiredShares > 0) {
throw `Threshold not met. Required Share: ${requiredShares}. You should reset your account.`;
}
// Reconstruct the Metadata Key
const metadataKey = await tKey.reconstructKey();
setMetadataKey(metadataKey?.privKey.toString("hex"));
const tssNonce: number = tKey.metadata.tssNonces![tKey.tssTag];
// tssShare1 = TSS Share from the social login/ service provider
const tssShare1PubKeyDetails = await tKey.serviceProvider.getTSSPubKey(tKey.tssTag, tssNonce);
const tssShare1PubKey = {
x: tssShare1PubKeyDetails.x.toString("hex"),
y: tssShare1PubKeyDetails.y.toString("hex"),
};
// tssShare2 = TSS Share from the local storage of the device
const { tssShare: tssShare2, tssIndex: tssShare2Index } = await tKey.getTSSShare(factorKey);
// derive tss pub key, tss pubkey is implicitly formed using the dkgPubKey and the userShare (as well as userTSSIndex)
const tssPubKey = getTSSPubKey(tssShare1PubKey, tssShare2, tssShare2Index);
const compressedTSSPubKey = Buffer.from(
`${tssPubKey.getX().toString(16, 64)}${tssPubKey.getY().toString(16, 64)}`,
"hex",
);
// save factor key and other metadata on local storage
if (
!existingUser ||
!(
tKeyLocalStore.verifier === loginResponse.userInfo.verifier &&
tKeyLocalStore.verifierId === loginResponse.userInfo.verifierId
)
) {
await addFactorKeyMetadata(tKey, factorKey, tssShare2, tssShare2Index, "local storage share");
}
await tKey.syncLocalMetadataTransitions();
setLocalFactorKey(factorKey);
const nodeDetails = await tKey.serviceProvider.getTSSNodeDetails();
// Set Signing Params for the Web3 Provider
setSigningParams({
tssNonce,
tssShare2,
tssShare2Index,
compressedTSSPubKey,
signatures,
nodeDetails,
});
// All done!
uiConsole(
"Successfully logged in & initialized MPC TKey SDK",
"TSS Public Key: ",
tKey.getTSSPub(),
"Metadata Key",
metadataKey.privKey.toString("hex"),
"With Factors/Shares:",
tKey.getMetadata().getShareDescription(),
);
} catch (error) {
uiConsole(error, "caught");
}
};
Setup Web3 Provider
Once you have set up your tKey instance, you're ready to set up the Web3 Provider. This is the provider that will be used by the Web3 SDK to sign the transactions.
import { setupWeb3 } from "./utils";
const [web3, setWeb3] = useState<any>(null);
const [signingParams, setSigningParams] = useState<any>(null);
useEffect(() => {
const localSetup = async () => {
const chainConfig = {
chainId: "0xaa36a7",
rpcTarget: "https://rpc.ankr.com/eth_sepolia",
displayName: "Ethereum Sepolia",
blockExplorer: "https://sepolia.etherscan.io",
ticker: "ETH",
tickerName: "Ethereum",
};
const web3Local = await setupWeb3(chainConfig, loginResponse, signingParams);
setWeb3(web3Local);
};
if (signingParams) {
localSetup();
}
}, [signingParams]);
Copy the Local TSS Share into a Manual Backup Factor Key
The following code snippet shows how to copy the local TSS share into a manual backup factor key. This is useful if you want to create a backup of your device share, without generating new TSS Shares. Generation of new TSS shares by default has been limited to 3 at the moment. This is to help save processing time and enhance the user experience.
import { copyExistingTSSShareForNewFactor, addFactorKeyMetadata } from "./utils";
const copyTSSShareIntoManualBackupFactorkey = async () => {
try {
if (!tKey) {
throw new Error("tkey does not exist, cannot add factor pub");
}
if (!localFactorKey) {
throw new Error("localFactorKey does not exist, cannot add factor pub");
}
const backupFactorKey = new BN(generatePrivate());
const backupFactorPub = getPubKeyPoint(backupFactorKey);
await copyExistingTSSShareForNewFactor(tKey, backupFactorPub, localFactorKey);
const { tssShare: tssShare2, tssIndex: tssIndex2 } = await tKey.getTSSShare(localFactorKey);
await addFactorKeyMetadata(tKey, backupFactorKey, tssShare2, tssIndex2, "manual share");
const serializedShare = await (tKey.modules.shareSerialization as any).serialize(
backupFactorKey,
"mnemonic",
);
await tKey.syncLocalMetadataTransitions();
uiConsole("Successfully created manual backup. Manual Backup Factor: ", serializedShare);
} catch (err) {
uiConsole(`Failed to copy share to new manual factor: ${err}`);
}
};
Create new TSS Share into a Manual Backup Factor Key
The following code snippet shows how to create a new TSS share into a manual backup factor key.
import { addNewTSSShareAndFactor, addFactorKeyMetadata } from "./utils";
const createNewTSSShareIntoManualBackupFactorkey = async () => {
try {
if (!tKey) {
throw new Error("tkey does not exist, cannot add factor pub");
}
if (!localFactorKey) {
throw new Error("localFactorKey does not exist, cannot add factor pub");
}
const backupFactorKey = new BN(generatePrivate());
const backupFactorPub = getPubKeyPoint(backupFactorKey);
const tKeyShareDescriptions = await tKey.getMetadata().getShareDescription();
let backupFactorIndex = 2;
for (const [key, value] of Object.entries(tKeyShareDescriptions)) {
console.log(`value of share ${key}`, value);
// eslint-disable-next-line no-loop-func, array-callback-return
value.map((factor: any) => {
factor = JSON.parse(factor);
if (factor.tssShareIndex > backupFactorIndex) {
backupFactorIndex = factor.tssShareIndex;
console.log(`backupFactorIndex of share ${key}`, backupFactorIndex);
}
});
}
uiConsole("backupFactorIndex:", backupFactorIndex + 1);
await addNewTSSShareAndFactor(
tKey,
backupFactorPub,
backupFactorIndex + 1,
localFactorKey,
signingParams.signatures,
);
const { tssShare: tssShare2, tssIndex: tssIndex2 } = await tKey.getTSSShare(backupFactorKey);
await addFactorKeyMetadata(tKey, backupFactorKey, tssShare2, tssIndex2, "manual share");
const serializedShare = await (tKey.modules.shareSerialization as any).serialize(
backupFactorKey,
"mnemonic",
);
await tKey.syncLocalMetadataTransitions();
uiConsole(" Successfully created manual backup.Manual Backup Factor: ", serializedShare);
} catch (err) {
uiConsole(`Failed to create new manual factor ${err}`);
}
};
Get Key Details
The following code snippet will help you get the details of the key stored on the metadata server. Please note these details can be customised and stored according to your needs.
const keyDetails = async () => {
if (!tKey) {
uiConsole("tKey not initialized yet");
return;
}
// const keyDetails = await tKey.getKeyDetails();
uiConsole(
"TSS Public Key: ",
tKey.getTSSPub(),
"With Factors/Shares:",
tKey.getMetadata().getShareDescription(),
);
// return keyDetails;
};
Interacting with Blockchain
Once you are done with the setting of the web3 provider, you can use it to make blockchain calls. This can be used with any EVM compatible chain
You can checkout our Connect Blockchain documentation which has a detailed guide on how to connect to major blockchains out there.
Example code
The code for the application we developed in this guide can be found in the examples repository. Check it out and try running it locally yourself!
Questions?
Ask us on Web3Auth's Community Support Portal