├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc.yaml
├── .vscode
└── settings.json
├── README.md
├── demo
└── redirect-flow-example
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
│ ├── src
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── flow.ts
│ ├── index.css
│ ├── index.tsx
│ └── logo.svg
│ ├── tsconfig.json
│ └── vite.config.js
├── package-lock.json
├── package.json
├── src
├── constants.ts
├── helper
│ ├── browserStorage.ts
│ ├── errors.ts
│ ├── factorSerialization.ts
│ ├── index.ts
│ └── securityQuestion.ts
├── index.ts
├── interfaces.ts
├── mpcCoreKit.ts
└── utils.ts
├── tests
├── .eslintrc.js
├── backwardCompatible.spec.ts
├── ed25519.spec.ts
├── factors.spec.ts
├── gating.spec.ts
├── importRecovery.spec.ts
├── login.spec.ts
├── securityQuestion.spec.ts
├── sessionTime.spec.ts
├── setup.ts
└── sfaImport.spec.ts
├── torus.config.js
├── tsconfig.json
└── webpack.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | #production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 | examples/
23 | types/
24 | dist/
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | require("@rushstack/eslint-patch/modern-module-resolution");
2 |
3 | module.exports = {
4 | root: true,
5 | extends: ["@toruslabs/eslint-config-typescript"],
6 | parser: "@typescript-eslint/parser",
7 | parserOptions: {
8 | sourceType: "module",
9 | ecmaVersion: 2022,
10 | project: "./tsconfig.json",
11 | },
12 | ignorePatterns: ["*.config.js", ".eslintrc.js"],
13 | rules: {},
14 | env: {
15 | es2020: true,
16 | browser: true,
17 | node: true,
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on: push
2 |
3 | name: tests
4 |
5 | jobs:
6 | test:
7 | name: run tests
8 | strategy:
9 | matrix:
10 | node: ["20.x"]
11 | os: [ubuntu-latest]
12 |
13 | runs-on: ${{ matrix.os }}
14 | steps:
15 | - name: Check out Git repository
16 | uses: actions/checkout@v4
17 |
18 | - name: Set up node
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: ${{ matrix.node }}
22 | cache: npm
23 |
24 | - name: Install dependencies
25 | run: npm install
26 |
27 | - name: Build
28 | run: npm run build
29 |
30 | - name: Run tests
31 | run: npm run test
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | */**/node_modules
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 | /dist
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | *.tgz
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | >=18.x
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | #production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 | types/
--------------------------------------------------------------------------------
/.prettierrc.yaml:
--------------------------------------------------------------------------------
1 | # .prettierrc or .prettierrc.yaml
2 | printWidth: 150
3 | singleQuote: false
4 | semi: true
5 | trailingComma: es5
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": false,
3 | "editor.formatOnType": true,
4 | "eslint.alwaysShowStatus": true,
5 | "eslint.debug": false,
6 | "eslint.format.enable": true,
7 | "eslint.lintTask.enable": true,
8 | "editor.codeActionsOnSave": {
9 | "source.fixAll": "explicit"
10 | }
11 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Web3Auth MPC Core Kit
2 |
3 | [](https://www.npmjs.com/package/@web3auth/mpc-core-kit/v/latest)
4 | [](https://bundlephobia.com/result?p=@web3auth/mpc-core-kit@latest)
5 |
6 | > Web3Auth is where passwordless auth meets non-custodial key infrastructure for Web3 apps and wallets. By aggregating OAuth (Google, Twitter, Discord) logins, different wallets and innovative Multi Party Computation (MPC) - Web3Auth provides a seamless login experience to every user on your application.
7 |
8 | Web3Auth MPC Core Kit Beta is a wrapper SDK that gives you all the needed functionalities for implementing the Web3Auth MPC features, giving you the flexibility of implementing your own UI and UX flows end to end.
9 |
10 | ## 📖 Documentation
11 |
12 | Checkout the official [Web3Auth Documentation](https://web3auth.io/docs/sdk/) to get started.
13 |
14 | ...and a lot more
15 |
16 | ## 🔗 Installation
17 |
18 | ```shell
19 | npm install --save @web3auth/mpc-core-kit
20 | ```
21 |
22 | ## ⚡ Quick Start
23 |
24 | ### Get your Client ID from Web3Auth Dashboard
25 |
26 | Hop on to the [Web3Auth Dashboard](https://dashboard.web3auth.io/) and create a new project. Use the Client ID of the project to start your integration.
27 |
28 | 
29 |
30 | ### Initialize Web3Auth for your preferred blockchain
31 |
32 | Web3Auth needs to initialise as soon as your app loads up to enable the user to log in. Preferably done within a constructor, initialisation is the step where you can pass on all the configurations for Web3Auth you want. A simple integration for Ethereum blockchain will look like this:
33 |
34 | ```js
35 | import { Web3AuthMPCCoreKit } from "@web3auth/mpc-core-kit";
36 |
37 | const DEFAULT_CHAIN_CONFIG: CustomChainConfig = {
38 | chainNamespace: CHAIN_NAMESPACES.EIP155,
39 | chainId: "0x5",
40 | rpcTarget: "https://rpc.ankr.com/eth_goerli",
41 | displayName: "Goerli Testnet",
42 | blockExplorer: "https://goerli.etherscan.io",
43 | ticker: "ETH",
44 | tickerName: "Ethereum",
45 | decimals: 18,
46 | };
47 |
48 | //Initialize within your constructor
49 | const web3auth = new Web3AuthMPCCoreKit({
50 | web3AuthClientId: 'YOUR_CLIENT_ID',
51 | web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET
52 | chainConfig: DEFAULT_CHAIN_CONFIG
53 | });
54 |
55 |
56 | await web3auth.init();
57 | ```
58 |
59 | ### Login your User
60 |
61 | Once you're done initialising, just create a button that triggers login for your preferred social channel for the user on their request. You can further use the returned provider for making RPC calls to the blockchain.
62 |
63 |
64 | ```js
65 | const verifierConfig = {
66 | subVerifierDetails: {
67 | typeOfLogin: 'google',
68 | verifier: 'w3a-google-demo',
69 | clientId:
70 | '519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com',
71 | }
72 | } as SubVerifierDetailsParams;
73 |
74 | await coreKitInstance.loginWithOAuth(verifierConfig);
75 | ```
76 |
77 | For JWT(idToken) login
78 | ```js
79 | const idTokenLoginParams = {
80 | verifier: "torus-test-health",
81 | verifierId: parsedToken.email,
82 | idToken,
83 | } as IdTokenLoginParams;
84 |
85 | await coreKitInstance.loginWithJWT(idTokenLoginParams);
86 | ```
87 |
88 |
89 |
90 | ## 🩹 Examples
91 |
92 | Checkout the examples for your preferred blockchain and platform in our [examples repository](https://github.com/Web3Auth/web3auth-core-kit-examples)
93 |
94 | ## 🌐 Demo
95 |
96 | Checkout the [Web3Auth Demo](https://demo-app.web3auth.io/) to see how Web3Auth can be used in your application.
97 |
98 | ## 💬 Troubleshooting and Support
99 |
100 | - Have a look at our [Community Portal](https://community.web3auth.io/) to see if anyone has any questions or issues you might be having. Feel free to reate new topics and we'll help you out as soon as possible.
101 | - Checkout our [Troubleshooting Documentation Page](https://web3auth.io/docs/troubleshooting) to know the common issues and solutions.
102 | - For Priority Support, please have a look at our [Pricing Page](https://web3auth.io/pricing.html) for the plan that suits your needs.
103 |
104 |
105 | ### Development steps:-
106 | #### Install dependencies:
107 | npm i
108 |
109 | #### Run tests:
110 | npm run test
111 |
112 | #### Build:
113 | npm run build
114 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
50 |
51 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redirect-flow-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@tkey/common-types": "^15.1.0",
7 | "@toruslabs/tss-dkls-lib": "^4.0.0",
8 | "@toruslabs/tss-frost-lib": "^1.0.0",
9 | "@types/jest": "^27.5.2",
10 | "@types/node": "^16.18.48",
11 | "@types/react": "^18.3.11",
12 | "@types/react-dom": "^18.3.0",
13 | "@vitejs/plugin-react": "^4.3.2",
14 | "@web3auth/base": "^9.0.2",
15 | "@web3auth/ethereum-mpc-provider": "^9.3.0",
16 | "@web3auth/mpc-core-kit": "file:../..",
17 | "browserify-zlib": "^0.2.0",
18 | "copy-webpack-plugin": "^11.0.0",
19 | "html-webpack-plugin": "^5.5.3",
20 | "jsonwebtoken": "^8.5.1",
21 | "jsrsasign": "^10.6.1",
22 | "react": "^18.3.1",
23 | "react-dom": "^18.3.1",
24 | "typescript": "^5.6.3",
25 | "web3": "^4.13.0"
26 | },
27 | "scripts": {
28 | "start": "vite",
29 | "dev": "vite",
30 | "build": "tsc && vite build",
31 | "serve": "vite preview"
32 | },
33 | "browserslist": {
34 | "production": [
35 | "chrome >= 67",
36 | "edge >= 79",
37 | "firefox >= 68",
38 | "opera >= 54",
39 | "safari >= 14"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | },
47 | "eslintConfig": {
48 | "extends": [
49 | "react-app",
50 | "react-app/jest"
51 | ]
52 | },
53 | "devDependencies": {
54 | "@types/eccrypto": "^1.1.6",
55 | "@types/elliptic": "^6.4.18",
56 | "@types/jsrsasign": "^10.5.14",
57 | "@types/node": "^18.11.18",
58 | "@types/react": "^18.3.11",
59 | "@types/react-dom": "^18.3.0",
60 | "assert": "^2.1.0",
61 | "bn.js": "^5.2.1",
62 | "browserify-zlib": "^0.2.0",
63 | "buffer": "^6.0.3",
64 | "copy-webpack-plugin": "^12.0.2",
65 | "crypto-browserify": "^3.12.0",
66 | "html-webpack-plugin": "^5.6.0",
67 | "https-browserify": "^1.0.0",
68 | "os-browserify": "^0.3.0",
69 | "path-browserify": "^1.0.1",
70 | "process": "^0.11.10",
71 | "stream-browserify": "^3.0.0",
72 | "stream-http": "^3.2.0",
73 | "url": "^0.11.4"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Web3Auth/mpc-core-kit/cb155e7beee8150e28dc2f3b32cabfe98722bfc2/demo/redirect-flow-example/public/favicon.ico
--------------------------------------------------------------------------------
/demo/redirect-flow-example/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Web3Auth/mpc-core-kit/cb155e7beee8150e28dc2f3b32cabfe98722bfc2/demo/redirect-flow-example/public/logo192.png
--------------------------------------------------------------------------------
/demo/redirect-flow-example/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Web3Auth/mpc-core-kit/cb155e7beee8150e28dc2f3b32cabfe98722bfc2/demo/redirect-flow-example/public/logo512.png
--------------------------------------------------------------------------------
/demo/redirect-flow-example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/src/App.css:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 60%;
3 | margin: auto;
4 | padding: 0 2rem;
5 | }
6 |
7 | .main {
8 | min-height: 100vh;
9 | padding: 4rem 0;
10 | flex: 1;
11 | display: flex;
12 | flex-direction: column;
13 | justify-content: center;
14 | align-items: center;
15 | }
16 |
17 | .title {
18 | line-height: 1.15;
19 | font-size: 3rem;
20 | text-align: center;
21 | margin: 50px;
22 | }
23 |
24 | .subtitle {
25 | line-height: 1.15;
26 | font-size: 1.5rem;
27 | text-align: center;
28 | margin: 10px;
29 | }
30 |
31 | .title a {
32 | color: #0070f3;
33 | text-decoration: none;
34 | }
35 |
36 | .grid {
37 | display: flex;
38 | align-items: center;
39 | flex-direction: column;
40 | }
41 |
42 | .card {
43 | margin: 0.5rem;
44 | padding: 0.7rem;
45 | text-align: center;
46 | color: #0070f3;
47 | background-color: #fafafa;
48 | text-decoration: none;
49 | border: 1px solid #0070f3;
50 | border-radius: 10px;
51 | transition: color 0.15s ease, border-color 0.15s ease;
52 | width: 100%;
53 | }
54 |
55 | .card:hover,
56 | .card:focus,
57 | .card:active {
58 | cursor: pointer;
59 | background-color: #f1f1f1;
60 | }
61 |
62 | .footer {
63 | display: flex;
64 | flex: 1;
65 | padding: 2rem 0;
66 | border-top: 1px solid #eaeaea;
67 | justify-content: center;
68 | align-items: center;
69 | margin-top: 10rem;
70 | }
71 |
72 | .footer a {
73 | display: flex;
74 | justify-content: center;
75 | align-items: center;
76 | flex-grow: 1;
77 | }
78 |
79 | .logo {
80 | height: 1.5rem;
81 | margin-left: 0.5rem;
82 | }
83 |
84 | @media (max-width: 1200px) {
85 | .container {
86 | width: 100%;
87 | }
88 | }
89 |
90 | .flex-container {
91 | display: flex;
92 | flex-flow: row wrap;
93 | justify-content: center;
94 | align-items: center;
95 | }
96 |
97 | .flex-container .card {
98 | width: fit-content;
99 | }
100 |
101 | .flex-container-top {
102 | display: flex;
103 | flex-flow: row wrap;
104 | justify-content: space-around;
105 | align-items: top;
106 | }
107 |
108 | #console {
109 | width: 100%;
110 | height: 100%;
111 | overflow: auto;
112 | word-wrap: break-word;
113 | font-size: 16px;
114 | font-family: monospace;
115 | }
116 |
117 | .disabledDiv {
118 | pointer-events: none;
119 | opacity: 0.4;
120 | }
121 |
122 | .flex-column {
123 | display: flex;
124 | flex-direction: column;
125 | }
--------------------------------------------------------------------------------
/demo/redirect-flow-example/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo, useRef, useState } from "react";
2 | import {
3 | Web3AuthMPCCoreKit,
4 | WEB3AUTH_NETWORK,
5 | SubVerifierDetailsParams,
6 | TssShareType,
7 | keyToMnemonic,
8 | COREKIT_STATUS,
9 | TssSecurityQuestion,
10 | generateFactorKey,
11 | mnemonicToKey,
12 | parseToken,
13 | factorKeyCurve,
14 | makeEthereumSigner,
15 | } from "@web3auth/mpc-core-kit";
16 | import Web3 from "web3";
17 | import { CHAIN_NAMESPACES, CustomChainConfig, IProvider } from "@web3auth/base";
18 | import { EthereumSigningProvider } from "@web3auth/ethereum-mpc-provider";
19 | import { BN } from "bn.js";
20 | import { KeyType, Point } from "@tkey/common-types";
21 | import { tssLib } from "@toruslabs/tss-dkls-lib";
22 | // import{ tssLib } from "@toruslabs/tss-frost-lib";
23 |
24 | import "./App.css";
25 | import jwt, { Algorithm } from "jsonwebtoken";
26 | import { flow } from "./flow";
27 |
28 | const uiConsole = (...args: any[]): void => {
29 | const el = document.querySelector("#console>p");
30 | if (el) {
31 | el.innerHTML = JSON.stringify(args || {}, null, 2);
32 | }
33 | console.log(...args);
34 | };
35 |
36 | const selectedNetwork = WEB3AUTH_NETWORK.DEVNET;
37 |
38 | const DEFAULT_CHAIN_CONFIG: CustomChainConfig = {
39 | chainNamespace: CHAIN_NAMESPACES.EIP155,
40 | chainId: "0xaa36a7",
41 | rpcTarget: "https://rpc.ankr.com/eth_sepolia",
42 | displayName: "Ethereum Sepolia Testnet",
43 | blockExplorerUrl: "https://sepolia.etherscan.io",
44 | ticker: "ETH",
45 | tickerName: "Ethereum",
46 | decimals: 18,
47 | };
48 |
49 | const coreKitInstance = new Web3AuthMPCCoreKit({
50 | web3AuthClientId: "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ",
51 | web3AuthNetwork: selectedNetwork,
52 | uxMode: "redirect",
53 | manualSync: true,
54 | storage: window.localStorage,
55 | // sessionTime: 3600, // <== can provide variable session time based on user subscribed plan
56 | tssLib,
57 | useDKG: false,
58 | });
59 |
60 | const privateKey = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCD7oLrcKae+jVZPGx52Cb/lKhdKxpXjl9eGNa1MlY57A==";
61 | const jwtPrivateKey = `-----BEGIN PRIVATE KEY-----\n${privateKey}\n-----END PRIVATE KEY-----`;
62 | const alg: Algorithm = "ES256";
63 | export const mockLogin = async (email: string) => {
64 | const iat = Math.floor(Date.now() / 1000);
65 | const payload = {
66 | iss: "torus-key-test",
67 | aud: "torus-key-test",
68 | name: email,
69 | email,
70 | scope: "email",
71 | iat,
72 | eat: iat + 120,
73 | };
74 |
75 | const algo = {
76 | expiresIn: 120,
77 | algorithm: alg,
78 | };
79 |
80 | const token = jwt.sign(payload, jwtPrivateKey, algo);
81 | const idToken = token;
82 | const parsedToken = parseToken(idToken);
83 | return { idToken, parsedToken };
84 | };
85 |
86 | function App() {
87 | const [mockEmail, setMockEmail] = useState(undefined);
88 |
89 | const [backupFactorKey, setBackupFactorKey] = useState(undefined);
90 | const [provider, setProvider] = useState(null);
91 | const [web3, setWeb3] = useState(undefined);
92 | const [exportTssShareType, setExportTssShareType] = useState(TssShareType.DEVICE);
93 | const [factorPubToDelete, setFactorPubToDelete] = useState("");
94 | const [coreKitStatus, setCoreKitStatus] = useState(COREKIT_STATUS.NOT_INITIALIZED);
95 | const [answer, setAnswer] = useState(undefined);
96 | const [newAnswer, setNewAnswer] = useState(undefined);
97 | const [question, setQuestion] = useState(undefined);
98 | const [newQuestion, setNewQuestion] = useState(undefined);
99 | const securityQuestion = useMemo(() => new TssSecurityQuestion(), []);
100 |
101 | async function setupProvider(chainConfig?: CustomChainConfig) {
102 | if (coreKitInstance.keyType !== KeyType.secp256k1) {
103 | console.warn(`Ethereum requires keytype ${KeyType.secp256k1}, skipping provider setup`);
104 | return;
105 | }
106 | let localProvider = new EthereumSigningProvider({ config: { chainConfig: chainConfig || DEFAULT_CHAIN_CONFIG } });
107 | localProvider.setupProvider(makeEthereumSigner(coreKitInstance));
108 | setProvider(localProvider);
109 | }
110 |
111 | // decide whether to rehydrate session
112 | const rehydrate = true;
113 | const initialized = useRef(false);
114 | useEffect(() => {
115 | const init = async () => {
116 | // Example config to handle redirect result manually
117 | if (coreKitInstance.status === COREKIT_STATUS.NOT_INITIALIZED) {
118 | await coreKitInstance.init({ handleRedirectResult: false, rehydrate });
119 | if (window.location.hash.includes("#state")) {
120 | await coreKitInstance.handleRedirectResult();
121 | }
122 | }
123 | if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) {
124 | await setupProvider();
125 | }
126 |
127 | if (coreKitInstance.status === COREKIT_STATUS.REQUIRED_SHARE) {
128 | uiConsole(
129 | "required more shares, please enter your backup/ device factor key, or reset account unrecoverable once reset, please use it with caution]"
130 | );
131 | }
132 |
133 | console.log("coreKitInstance.status", coreKitInstance.status);
134 | setCoreKitStatus(coreKitInstance.status);
135 |
136 | try {
137 | let result = securityQuestion.getQuestion(coreKitInstance!);
138 | setQuestion(result);
139 | uiConsole("security question set");
140 | } catch (e) {
141 | uiConsole("security question not set");
142 | }
143 | };
144 | if (!initialized.current)
145 | {
146 | init();
147 | initialized.current = true;
148 | }
149 |
150 | }, []);
151 |
152 | useEffect(() => {
153 | if (provider) {
154 | const web3 = new Web3(provider as IProvider);
155 | setWeb3(web3);
156 | }
157 | }, [provider]);
158 |
159 | const keyDetails = async () => {
160 | if (!coreKitInstance) {
161 | throw new Error("coreKitInstance not found");
162 | }
163 | uiConsole(coreKitInstance.getKeyDetails());
164 | };
165 |
166 | const listFactors = async () => {
167 | if (!coreKitInstance) {
168 | throw new Error("coreKitInstance not found");
169 | }
170 | const factorPubs = coreKitInstance.tKey.metadata.factorPubs;
171 | if (!factorPubs) {
172 | throw new Error("factorPubs not found");
173 | }
174 | const pubsHex = factorPubs[coreKitInstance.tKey.tssTag].map((pub: Point) => {
175 | return pub.toSEC1(factorKeyCurve, true).toString("hex");
176 | });
177 | uiConsole(pubsHex);
178 | };
179 |
180 | const loginWithMock = async () => {
181 | try {
182 | if (!mockEmail) {
183 | throw new Error("mockEmail not found");
184 | }
185 | const { idToken, parsedToken } = await mockLogin(mockEmail);
186 | await coreKitInstance.loginWithJWT({
187 | verifier: "torus-test-health",
188 | verifierId: parsedToken.email,
189 | idToken,
190 | prefetchTssPublicKeys: 1,
191 | });
192 |
193 | if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) {
194 | await setupProvider();
195 | }
196 | setCoreKitStatus(coreKitInstance.status);
197 | } catch (error: unknown) {
198 | console.error(error);
199 | }
200 | };
201 |
202 | const timedFlow = async () => {
203 | try {
204 | if (!mockEmail) {
205 | throw new Error("mockEmail not found");
206 | }
207 | const { idToken, parsedToken } = await mockLogin(mockEmail);
208 | await flow({
209 | selectedNetwork,
210 | manualSync: true,
211 | setupProviderOnInit: false,
212 | verifier: "torus-test-health",
213 | verifierId: parsedToken.email,
214 | idToken,
215 | });
216 | } catch (error: unknown) {
217 | console.error(error);
218 | }
219 | };
220 |
221 | const login = async () => {
222 | try {
223 | // Triggering Login using Service Provider ==> opens the popup
224 | if (!coreKitInstance) {
225 | throw new Error("initiated to login");
226 | }
227 | const verifierConfig = {
228 | subVerifierDetails: {
229 | typeOfLogin: "google",
230 | verifier: "w3a-google-demo",
231 | clientId: "519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com",
232 | },
233 | } as SubVerifierDetailsParams;
234 |
235 | await coreKitInstance.loginWithOAuth(verifierConfig);
236 | setCoreKitStatus(coreKitInstance.status);
237 | } catch (error: unknown) {
238 | console.error(error);
239 | }
240 | };
241 |
242 | const getDeviceShare = async () => {
243 | const factorKey = await coreKitInstance!.getDeviceFactor();
244 | setBackupFactorKey(factorKey);
245 | uiConsole("Device share: ", factorKey);
246 | };
247 |
248 | const inputBackupFactorKey = async () => {
249 | if (!coreKitInstance) {
250 | throw new Error("coreKitInstance not found");
251 | }
252 | if (!backupFactorKey) {
253 | throw new Error("backupFactorKey not found");
254 | }
255 | const factorKey = new BN(backupFactorKey, "hex");
256 | await coreKitInstance.inputFactorKey(factorKey);
257 |
258 | if (coreKitInstance.status === COREKIT_STATUS.REQUIRED_SHARE) {
259 | uiConsole(
260 | "required more shares even after inputing backup factor key, please enter your backup/ device factor key, or reset account [unrecoverable once reset, please use it with caution]"
261 | );
262 | }
263 |
264 | if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) {
265 | await setupProvider();
266 | }
267 |
268 | setCoreKitStatus(coreKitInstance.status);
269 | };
270 |
271 | const recoverSecurityQuestionFactor = async () => {
272 | if (!coreKitInstance) {
273 | throw new Error("coreKitInstance not found");
274 | }
275 | if (!answer) {
276 | throw new Error("backupFactorKey not found");
277 | }
278 |
279 | let factorKey = await securityQuestion.recoverFactor(coreKitInstance, answer);
280 | setBackupFactorKey(factorKey);
281 | uiConsole("Security Question share: ", factorKey);
282 | };
283 |
284 | const logout = async () => {
285 | if (!coreKitInstance) {
286 | throw new Error("coreKitInstance not found");
287 | }
288 | await coreKitInstance.logout();
289 | uiConsole("Log out");
290 | setProvider(null);
291 | setCoreKitStatus(coreKitInstance.status);
292 | };
293 |
294 | const getUserInfo = (): void => {
295 | const user = coreKitInstance?.getUserInfo();
296 | uiConsole(user);
297 | };
298 |
299 | const exportFactor = async (): Promise => {
300 | if (!coreKitInstance) {
301 | throw new Error("coreKitInstance is not set");
302 | }
303 | uiConsole("export share type: ", exportTssShareType);
304 | const factorKey = generateFactorKey();
305 | await coreKitInstance.createFactor({
306 | shareType: exportTssShareType,
307 | factorKey: factorKey.private,
308 | });
309 | let mnemonic = keyToMnemonic(factorKey.private.toString("hex"));
310 | let key = mnemonicToKey(mnemonic);
311 |
312 | uiConsole("Export factor key: ", factorKey);
313 | console.log("menmonic : ", mnemonic);
314 | console.log("key: ", key);
315 | };
316 |
317 | const deleteFactor = async (): Promise => {
318 | if (!coreKitInstance) {
319 | throw new Error("coreKitInstance is not set");
320 | }
321 | const pubBuffer = Buffer.from(factorPubToDelete, "hex");
322 | const pub = Point.fromSEC1(factorKeyCurve, pubBuffer.toString("hex"));
323 | await coreKitInstance.deleteFactor(pub);
324 | uiConsole("factor deleted");
325 | };
326 |
327 | const getChainID = async () => {
328 | if (!web3) {
329 | uiConsole("web3 not initialized yet");
330 | return;
331 | }
332 | const chainId = await web3.eth.getChainId();
333 | uiConsole(chainId);
334 | return chainId;
335 | };
336 |
337 | const setTSSWalletIndex = async (index = 0) => {
338 | await coreKitInstance.setTssWalletIndex(index);
339 | // log new account details
340 | await getAccounts();
341 | };
342 |
343 | const getAccounts = async () => {
344 | if (!web3) {
345 | uiConsole("web3 not initialized yet");
346 | return;
347 | }
348 | const address = (await web3.eth.getAccounts())[0];
349 | uiConsole(address);
350 | return address;
351 | };
352 |
353 | const getBalance = async () => {
354 | if (!web3) {
355 | uiConsole("web3 not initialized yet");
356 | return;
357 | }
358 | const address = (await web3.eth.getAccounts())[0];
359 | const balance = web3.utils.fromWei(
360 | await web3.eth.getBalance(address) // Balance is in wei
361 | );
362 | uiConsole(balance);
363 | return balance;
364 | };
365 |
366 | const signMessage = async (): Promise => {
367 | if (coreKitInstance.keyType === "secp256k1") {
368 | if (!web3) {
369 | uiConsole("web3 not initialized yet");
370 | return;
371 | }
372 | const fromAddress = (await web3.eth.getAccounts())[0];
373 |
374 | const message = "hello";
375 | const signedMessage = await web3.eth.personal.sign(message, fromAddress, "");
376 |
377 |
378 | uiConsole(signedMessage);
379 | } else if (coreKitInstance.keyType === "ed25519") {
380 | const msg = Buffer.from("hello signer!");
381 | const sig = await coreKitInstance.sign(msg);
382 | uiConsole(sig.toString("hex"));
383 | }
384 | };
385 | const signMessageWithPrecomputedTss = async (): Promise => {
386 | if (coreKitInstance.keyType === "secp256k1") {
387 | const precomputedTssClient = await coreKitInstance.precompute_secp256k1();
388 | const msg = Buffer.from("hello signer!");
389 | const sig = await coreKitInstance.sign(msg, false, precomputedTssClient);
390 | uiConsole(sig.toString("hex"));
391 | } else if (coreKitInstance.keyType === "ed25519") {
392 | const msg = Buffer.from("hello signer!");
393 | const sig = await coreKitInstance.sign(msg);
394 | uiConsole(sig.toString("hex"));
395 | }
396 | };
397 |
398 | const signMultipleMessagesWithPrecomputedTss = async (): Promise => {
399 | if (coreKitInstance.keyType === "secp256k1") {
400 | const [precomputedTssClient, precomputedTssClient2] = await Promise.all([coreKitInstance.precompute_secp256k1(), coreKitInstance.precompute_secp256k1()]);
401 |
402 | const msg = Buffer.from("hello signer!");
403 | const sig = await coreKitInstance.sign(msg, false, precomputedTssClient);
404 | const msg2 = Buffer.from("hello signer2!");
405 |
406 | const sig2 = await coreKitInstance.sign(msg2, false, precomputedTssClient2);
407 | uiConsole("Sig1: ", sig.toString("hex"), "Sig2: ", sig2.toString("hex"));
408 | } else if (coreKitInstance.keyType === "ed25519") {
409 | const msg = Buffer.from("hello signer!");
410 | const sig = await coreKitInstance.sign(msg);
411 | uiConsole(sig.toString("hex"));
412 | }
413 | };
414 | const switchChainSepolia = async () => {
415 | if (!provider) {
416 | uiConsole("provider not initialized yet");
417 | return;
418 | }
419 | const newChainConfig = {
420 | chainId: "0xaa36a7", // for wallet connect make sure to pass in this chain in the loginSettings of the adapter.
421 | displayName: "Ethereum Sepolia",
422 | chainNamespace: CHAIN_NAMESPACES.EIP155,
423 | tickerName: "Ethereum Sepolia",
424 | ticker: "ETH",
425 | decimals: 18,
426 | rpcTarget: "https://rpc.ankr.com/eth_sepolia",
427 | blockExplorer: "https://sepolia.etherscan.io",
428 | logo: "https://cryptologos.cc/logos/ethereum-eth-logo.png",
429 | };
430 |
431 | if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) {
432 | await setupProvider(newChainConfig);
433 | }
434 | uiConsole("Changed to Sepolia Network");
435 | };
436 |
437 | const switchChainPolygon = async () => {
438 | if (!provider) {
439 | uiConsole("provider not initialized yet");
440 | return;
441 | }
442 | const newChainConfig = {
443 | chainNamespace: CHAIN_NAMESPACES.EIP155,
444 | chainId: "0x89", // hex of 137, polygon mainnet
445 | rpcTarget: "https://rpc.ankr.com/polygon",
446 | // Avoid using public rpcTarget in production.
447 | // Use services like Infura, Quicknode etc
448 | displayName: "Polygon Mainnet",
449 | blockExplorer: "https://polygonscan.com",
450 | ticker: "MATIC",
451 | tickerName: "MATIC",
452 | };
453 | if (coreKitInstance.status === COREKIT_STATUS.LOGGED_IN) {
454 | await setupProvider(newChainConfig);
455 | }
456 | uiConsole("Changed to Sepolia Network");
457 | };
458 |
459 | const switchChainOPBNB = async () => {
460 | if (!provider) {
461 | uiConsole("provider not initialized yet");
462 | return;
463 | }
464 |
465 | console.log(provider);
466 | let newChainConfig = {
467 | chainId: "0xCC",
468 | chainName: "BNB",
469 | nativeCurrency: {
470 | name: "BNB",
471 | symbol: "BNB",
472 | decimals: 18,
473 | },
474 | rpcUrls: ["https://opbnb-mainnet-rpc.bnbchain.org"],
475 | blockExplorerUrls: ["https://opbnbscan.com"],
476 | };
477 | await provider.sendAsync({
478 | method: "wallet_addEthereumChain",
479 | params: [newChainConfig],
480 | });
481 | await provider.sendAsync({
482 | method: "wallet_switchEthereumChain",
483 | params: [{ chainId: newChainConfig.chainId }],
484 | });
485 | uiConsole("Changed to BNB Network");
486 | };
487 |
488 | const criticalResetAccount = async (): Promise => {
489 | // This is a critical function that should only be used for testing purposes
490 | // Resetting your account means clearing all the metadata associated with it from the metadata server
491 | // The key details will be deleted from our server and you will not be able to recover your account
492 | if (!coreKitInstance) {
493 | throw new Error("coreKitInstance is not set");
494 | }
495 | //@ts-ignore
496 | // if (selectedNetwork === WEB3AUTH_NETWORK.MAINNET) {
497 | // throw new Error("reset account is not recommended on mainnet");
498 | // }
499 | await coreKitInstance.tKey.storageLayer.setMetadata({
500 | privKey: new BN(coreKitInstance.state.postBoxKey!, "hex"),
501 | input: { message: "KEY_NOT_FOUND" },
502 | });
503 | uiConsole("reset");
504 | setProvider(null);
505 | };
506 |
507 | const sendTransaction = async () => {
508 | if (!web3) {
509 | uiConsole("web3 not initialized yet");
510 | return;
511 | }
512 | const fromAddress = (await web3.eth.getAccounts())[0];
513 |
514 | const destination = "0x2E464670992574A613f10F7682D5057fB507Cc21";
515 | const amount = web3.utils.toWei("0.0001"); // Convert 1 ether to wei
516 |
517 | // Submit transaction to the blockchain and wait for it to be mined
518 | uiConsole("Sending transaction...");
519 | const receipt = await web3.eth.sendTransaction({
520 | from: fromAddress,
521 | to: destination,
522 | value: amount,
523 | });
524 | uiConsole(receipt);
525 | };
526 |
527 | const createSecurityQuestion = async (question: string, answer: string) => {
528 | if (!coreKitInstance) {
529 | throw new Error("coreKitInstance is not set");
530 | }
531 | await securityQuestion.setSecurityQuestion({ mpcCoreKit: coreKitInstance, question, answer, shareType: TssShareType.RECOVERY });
532 | setNewQuestion(undefined);
533 | let result = await securityQuestion.getQuestion(coreKitInstance);
534 | if (result) {
535 | setQuestion(question);
536 | }
537 | };
538 |
539 | const changeSecurityQuestion = async (newQuestion: string, newAnswer: string, answer: string) => {
540 | if (!coreKitInstance) {
541 | throw new Error("coreKitInstance is not set");
542 | }
543 | await securityQuestion.changeSecurityQuestion({ mpcCoreKit: coreKitInstance, newQuestion, newAnswer, answer });
544 | let result = await securityQuestion.getQuestion(coreKitInstance);
545 | if (result) {
546 | setQuestion(question);
547 | }
548 | };
549 |
550 | const deleteSecurityQuestion = async () => {
551 | if (!coreKitInstance) {
552 | throw new Error("coreKitInstance is not set");
553 | }
554 | await securityQuestion.deleteSecurityQuestion(coreKitInstance);
555 | setQuestion(undefined);
556 | };
557 |
558 | const enableMFA = async () => {
559 | if (!coreKitInstance) {
560 | throw new Error("coreKitInstance is not set");
561 | }
562 | const factorKey = await coreKitInstance.enableMFA({});
563 | const factorKeyMnemonic = await keyToMnemonic(factorKey);
564 |
565 | uiConsole("MFA enabled, device factor stored in local store, deleted hashed cloud key, your backup factor key: ", factorKeyMnemonic);
566 | };
567 |
568 | const commit = async () => {
569 | if (!coreKitInstance) {
570 | throw new Error("coreKitInstance is not set");
571 | }
572 | await coreKitInstance.commitChanges();
573 | };
574 |
575 | const loggedInView = (
576 | <>
577 | Account Details
578 |
579 |
582 |
583 |
586 |
587 |
590 |
591 |
594 |
595 |
598 |
599 |
600 |
603 |
604 |
607 |
608 |
611 |
612 | Recovery/ Key Manipulation
613 |
614 |
Enabling MFA
615 |
616 |
619 |
620 |
Manual Factors Manipulation
621 |
622 |
623 |
627 |
630 |
631 |
632 |
633 | setFactorPubToDelete(e.target.value)}>
634 |
637 |
638 |
639 | setBackupFactorKey(e.target.value)}>
640 |
643 |
644 |
645 |
Security Question
646 |
647 |
{question}
648 |
668 |
669 |
670 |
673 |
674 |
675 |
676 | Blockchain Calls
677 |
678 |
681 |
682 |
685 |
686 |
689 |
692 |
695 |
696 |
699 |
700 |
703 |
704 |
707 |
708 |
711 |
712 |
715 |
716 |
719 |
720 |
723 |
724 |
727 |
728 | >
729 | );
730 |
731 | const unloggedInView = (
732 | <>
733 | setMockEmail(e.target.value)}>
734 |
737 |
738 |
741 |
742 |
745 |
746 |
setBackupFactorKey(e.target.value)}>
747 |
750 |
753 |
754 |
755 |
756 |
757 | setAnswer(e.target.value)}>
758 |
761 |
762 |
763 |
766 | >
767 | );
768 |
769 | return (
770 |
771 |
777 |
778 |
{coreKitStatus === COREKIT_STATUS.LOGGED_IN ? loggedInView : unloggedInView}
779 |
782 |
783 |
792 |
793 | );
794 | }
795 |
796 | export default App;
797 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/src/flow.ts:
--------------------------------------------------------------------------------
1 |
2 | import { WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit, TssSecurityQuestion } from "@web3auth/mpc-core-kit";
3 | import { tssLib } from "@toruslabs/tss-dkls-lib";
4 |
5 | export const flow = async (params: { selectedNetwork: WEB3AUTH_NETWORK_TYPE, manualSync: boolean, setupProviderOnInit: boolean, verifier: string, verifierId: string, idToken: string }) => {
6 | const startTime = Date.now();
7 | console.log("startTime", startTime);
8 |
9 | const coreKitInstance = new Web3AuthMPCCoreKit(
10 | {
11 | web3AuthClientId: 'BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ',
12 | web3AuthNetwork: params.selectedNetwork,
13 | uxMode: 'redirect',
14 | manualSync: params.manualSync,
15 | storage: window.localStorage,
16 | tssLib,
17 | }
18 | );
19 |
20 | await coreKitInstance.init({ handleRedirectResult: false, rehydrate: false });
21 |
22 | await coreKitInstance.loginWithJWT({
23 | verifier: params.verifier,
24 | verifierId: params.verifierId,
25 | idToken: params.idToken,
26 | prefetchTssPublicKeys: 2,
27 | });
28 |
29 | let loggedInTime = Date.now();
30 | console.log("logged Time :", loggedInTime);
31 | console.log(loggedInTime - startTime);
32 |
33 | const securityQuestion: TssSecurityQuestion = new TssSecurityQuestion();
34 |
35 | await securityQuestion.setSecurityQuestion({
36 | mpcCoreKit: coreKitInstance,
37 | question: "question",
38 | answer: "answer",
39 | });
40 |
41 | let SqFactorTime = Date.now();
42 | console.log("SQ time", SqFactorTime);
43 | console.log(SqFactorTime - loggedInTime);
44 |
45 | await coreKitInstance.commitChanges();
46 |
47 | let commitTime = Date.now();
48 | console.log("commit :", commitTime);
49 | console.log(commitTime - SqFactorTime);
50 |
51 | console.log("total time", commitTime - startTime);
52 |
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/src/index.tsx:
--------------------------------------------------------------------------------
1 | import "./index.css";
2 |
3 | import React from "react";
4 | import ReactDOM from "react-dom/client";
5 |
6 | import App from "./App";
7 |
8 | const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
9 | root.render(
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/redirect-flow-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "allowSyntheticDefaultImports": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "isolatedModules": true,
8 | "jsx": "react-jsx",
9 | "lib": ["dom", "dom.iterable", "esnext"],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "noEmit": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "resolveJsonModule": true,
15 | "skipLibCheck": true,
16 | "strict": true,
17 | "target": "ESNext",
18 | "types": ["vite/client"]
19 | },
20 | "include": ["src"]
21 | }
22 |
23 |
24 | // {
25 | // "compilerOptions": {
26 |
27 | // "allowJs": false,
28 | // "allowSyntheticDefaultImports": true,
29 | // "esModuleInterop": true,
30 | // "forceConsistentCasingInFileNames": true,
31 | // "lib": ["dom", "dom.iterable", "esnext"],
32 | // "module": "esnext",
33 | // "moduleResolution": "node",
34 | // "isolatedModules": true,
35 | // "noEmit": true,
36 | // "jsx": "react-jsx",
37 | // "noFallthroughCasesInSwitch": true,
38 | // "resolveJsonModule": true,
39 | // "skipLibCheck": true,
40 | // "strict": true,
41 | // "target": "ESNext",
42 | // "types": ["vite/client"]
43 | // },
44 | // "include": [
45 | // "src"
46 | // ]
47 | // }
--------------------------------------------------------------------------------
/demo/redirect-flow-example/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | resolve: {
7 | alias: {
8 | stream: 'stream-browserify',
9 | crypto: 'crypto-browserify',
10 | path: 'path-browserify',
11 | os: 'os-browserify',
12 | https: 'https-browserify',
13 | http: 'stream-http',
14 | assert: 'assert',
15 | url: 'url',
16 | zlib: 'browserify-zlib',
17 | },
18 | },
19 | define: {
20 | global: "globalThis",
21 | },
22 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@web3auth/mpc-core-kit",
3 | "version": "3.4.3",
4 | "description": "MPC CoreKit SDK for web3Auth",
5 | "keywords": [
6 | "web3Auth/mpc-core-kit",
7 | "web3Auth",
8 | "MPC",
9 | "blockchain",
10 | "ethereum"
11 | ],
12 | "main": "dist/lib.cjs/index.js",
13 | "module": "dist/lib.esm/index.js",
14 | "unpkg": "dist/mpcCoreKit.umd.min.js",
15 | "jsdelivr": "dist/mpcCoreKit.umd.min.js",
16 | "types": "dist/types/index.d.ts",
17 | "author": "Torus Labs",
18 | "homepage": "https://github.com/Web3Auth/mpc-core-kit/tree/master#readme",
19 | "license": "ISC",
20 | "scripts": {
21 | "test": "node --test -r esbuild-register tests/*.spec.ts",
22 | "dev": "torus-scripts start",
23 | "build": "torus-scripts build",
24 | "release": "torus-scripts release",
25 | "lint": "eslint --fix 'src/**/*.ts'",
26 | "prepack": "npm run build",
27 | "pre-commit": "lint-staged --cwd ."
28 | },
29 | "files": [
30 | "dist"
31 | ],
32 | "peerDependencies": {
33 | "@babel/runtime": "^7.x",
34 | "@toruslabs/tss-dkls-lib": "^4.0.0",
35 | "@toruslabs/tss-frost-lib": "^1.0.0"
36 | },
37 | "peerDependenciesMeta": {
38 | "@toruslabs/tss-dkls-lib": {
39 | "optional": true
40 | },
41 | "@toruslabs/tss-frost-lib": {
42 | "optional": true
43 | }
44 | },
45 | "dependencies": {
46 | "@tkey/common-types": "^15.1.0",
47 | "@tkey/core": "^15.1.0",
48 | "@tkey/share-serialization": "^15.1.0",
49 | "@tkey/storage-layer-torus": "^15.1.0",
50 | "@tkey/tss": "^15.1.0",
51 | "@toruslabs/constants": "^14.0.0",
52 | "@toruslabs/customauth": "^20.3.0",
53 | "@toruslabs/elliptic-wrapper": "^0.1.0",
54 | "@toruslabs/fetch-node-details": "^14.0.1",
55 | "@toruslabs/fnd-base": "^14.0.0",
56 | "@toruslabs/metadata-helpers": "^6.0.0",
57 | "@toruslabs/session-manager": "^3.1.0",
58 | "@toruslabs/openlogin-utils": "^8.2.1",
59 | "@toruslabs/torus.js": "^15.1.0",
60 | "@toruslabs/tss-client": "^3.1.0",
61 | "@toruslabs/tss-frost-client": "0.3.1",
62 | "@toruslabs/tss-frost-common": "^1.0.1",
63 | "bn.js": "^5.2.1",
64 | "bowser": "^2.11.0",
65 | "elliptic": "^6.5.7",
66 | "loglevel": "^1.9.2"
67 | },
68 | "devDependencies": {
69 | "@babel/register": "^7.25.7",
70 | "@toruslabs/config": "^2.2.0",
71 | "@toruslabs/eslint-config-typescript": "^3.3.3",
72 | "@toruslabs/torus-scripts": "^6.1.2",
73 | "@toruslabs/tss-dkls-lib": "^4.0.0",
74 | "@toruslabs/tss-frost-lib": "^1.0.0",
75 | "@types/chai": "^4.3.16",
76 | "@types/elliptic": "^6.4.18",
77 | "@types/jsonwebtoken": "^9.0.7",
78 | "@types/node": "^20.14.0",
79 | "@typescript-eslint/eslint-plugin": "^6.7.0",
80 | "chai": "^5.1.1",
81 | "cross-env": "^7.0.3",
82 | "dotenv": "^16.4.5",
83 | "esbuild-register": "^3.6.0",
84 | "eslint": "^8.56.0",
85 | "husky": "^9.1.6",
86 | "jsonwebtoken": "^9.0.2",
87 | "lint-staged": "^15.2.10",
88 | "mocha": "^10.7.3",
89 | "node-fetch": "^3.3.2",
90 | "prettier": "^3.3.3",
91 | "rimraf": "^6.0.1",
92 | "ts-node": "^10.9.2",
93 | "tsconfig-paths": "^4.2.0",
94 | "tsconfig-paths-webpack-plugin": "^4.1.0",
95 | "tslib": "^2.7.0",
96 | "typescript": "^5.6.3"
97 | },
98 | "engines": {
99 | "node": ">=20.x"
100 | },
101 | "lint-staged": {
102 | "!(*d).ts": [
103 | "eslint --cache --fix",
104 | "prettier --write"
105 | ]
106 | },
107 | "publishConfig": {
108 | "access": "public"
109 | },
110 | "repository": {
111 | "type": "git",
112 | "url": "git+https://github.com/Web3Auth/mpc-core-kit"
113 | },
114 | "bugs": {
115 | "url": "https://github.com/Web3Auth/mpc-core-kit/issues"
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | import { TORUS_SAPPHIRE_NETWORK } from "@toruslabs/constants";
2 |
3 | export const WEB3AUTH_NETWORK = {
4 | MAINNET: TORUS_SAPPHIRE_NETWORK.SAPPHIRE_MAINNET,
5 | DEVNET: TORUS_SAPPHIRE_NETWORK.SAPPHIRE_DEVNET,
6 | } as const;
7 |
8 | export const USER_PATH = {
9 | NEW: "NewAccount",
10 | EXISTING: "ExistingAccount",
11 | REHYDRATE: "RehydrateAccount",
12 | RECOVER: "RecoverAccount",
13 | } as const;
14 |
15 | export enum FactorKeyTypeShareDescription {
16 | HashedShare = "hashedShare",
17 | SecurityQuestions = "tssSecurityQuestions",
18 | DeviceShare = "deviceShare",
19 | SeedPhrase = "seedPhrase",
20 | PasswordShare = "passwordShare",
21 | SocialShare = "socialShare",
22 | Other = "Other",
23 | }
24 |
25 | export const DELIMITERS = {
26 | Delimiter1: "\u001c",
27 | Delimiter2: "\u0015",
28 | Delimiter3: "\u0016",
29 | Delimiter4: "\u0017",
30 | };
31 |
32 | export const ERRORS = {
33 | TKEY_SHARES_REQUIRED: "required more shares",
34 | INVALID_BACKUP_SHARE: "invalid backup share",
35 | };
36 |
37 | export const SOCIAL_FACTOR_INDEX = 1;
38 |
39 | /**
40 | * Defines the TSS Share Index in a simplified way for better implementation.
41 | **/
42 | export enum TssShareType {
43 | DEVICE = 2,
44 | RECOVERY = 3,
45 | }
46 |
47 | export const VALID_SHARE_INDICES = [TssShareType.DEVICE, TssShareType.RECOVERY];
48 |
49 | export const SCALAR_LEN = 32; // Length of secp256k1 scalar in bytes.
50 | export const FIELD_ELEMENT_HEX_LEN = 32 * 2; // Length of secp256k1 field element in hex form.
51 |
52 | export const MAX_FACTORS = 10; // Maximum number of factors that can be added to an account.
53 | export const SOCIAL_TKEY_INDEX = 1;
54 |
--------------------------------------------------------------------------------
/src/helper/browserStorage.ts:
--------------------------------------------------------------------------------
1 | import { IAsyncStorage, IStorage } from "../interfaces";
2 | import CoreKitError from "./errors";
3 |
4 | export class MemoryStorage implements IStorage {
5 | private _store: Record = {};
6 |
7 | getItem(key: string): string | null {
8 | return this._store[key] || null;
9 | }
10 |
11 | setItem(key: string, value: string): void {
12 | this._store[key] = value;
13 | }
14 |
15 | removeItem(key: string): void {
16 | delete this._store[key];
17 | }
18 |
19 | clear(): void {
20 | this._store = {};
21 | }
22 | }
23 |
24 | export class AsyncStorage {
25 | public storage: IAsyncStorage | IStorage;
26 |
27 | private _storeKey: string;
28 |
29 | constructor(storeKey: string, storage: IAsyncStorage | IStorage) {
30 | this.storage = storage;
31 | this._storeKey = storeKey;
32 | }
33 |
34 | async toJSON(): Promise {
35 | const result = await this.storage.getItem(this._storeKey);
36 | if (!result) {
37 | throw CoreKitError.noDataFoundInStorage(`No data found in storage under key '${this._storeKey}'.`);
38 | }
39 | return result;
40 | }
41 |
42 | async resetStore(): Promise> {
43 | const currStore = await this.getStore();
44 | await this.storage.setItem(this._storeKey, JSON.stringify({}));
45 | return currStore;
46 | }
47 |
48 | async getStore(): Promise> {
49 | return JSON.parse((await this.storage.getItem(this._storeKey)) || "{}");
50 | }
51 |
52 | async get(key: string): Promise {
53 | const store = JSON.parse((await this.storage.getItem(this._storeKey)) || "{}");
54 | return store[key];
55 | }
56 |
57 | async set(key: string, value: T): Promise {
58 | const store = JSON.parse((await this.storage.getItem(this._storeKey)) || "{}");
59 | store[key] = value;
60 | await this.storage.setItem(this._storeKey, JSON.stringify(store));
61 | }
62 |
63 | async remove(key: string): Promise {
64 | const store = JSON.parse((await this.storage.getItem(this._storeKey)) || "{}");
65 | delete store[key];
66 | await this.storage.setItem(this._storeKey, JSON.stringify(store));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/helper/errors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Fix the prototype chain of the error
3 | *
4 | * Use Object.setPrototypeOf
5 | * Support ES6 environments
6 | *
7 | * Fallback setting __proto__
8 | * Support IE11+, see https://docs.microsoft.com/en-us/scripting/javascript/reference/javascript-version-information
9 | */
10 | function fixProto(target: Error, prototype: object) {
11 | const { setPrototypeOf } = Object;
12 | if (setPrototypeOf) {
13 | setPrototypeOf(target, prototype);
14 | } else {
15 | // eslint-disable-next-line no-proto, @typescript-eslint/no-explicit-any
16 | (target as any).__proto__ = prototype;
17 | }
18 | }
19 |
20 | /**
21 | * Capture and fix the error stack when available
22 | *
23 | * Use Error.captureStackTrace
24 | * Support v8 environments
25 | */
26 | function fixStack(target: Error, fn = target.constructor) {
27 | const { captureStackTrace } = Error;
28 | if (captureStackTrace) {
29 | captureStackTrace(target, fn);
30 | }
31 | }
32 |
33 | // copy from https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2022.error.d.ts
34 | // avoid typescript isue https://github.com/adriengibrat/ts-custom-error/issues/81
35 | interface ErrorOptions {
36 | cause?: unknown;
37 | }
38 |
39 | /**
40 | * Allows to easily extend a base class to create custom applicative errors.
41 | *
42 | * example:
43 | * ```
44 | * class HttpError extends CustomError {
45 | * public constructor(
46 | * public code: number,
47 | * message?: string,
48 | * cause?: Error,
49 | * ) {
50 | * super(message, { cause })
51 | * }
52 | * }
53 | *
54 | * new HttpError(404, 'Not found')
55 | * ```
56 | */
57 | export class CustomError extends Error {
58 | name: string;
59 |
60 | constructor(message?: string, options?: ErrorOptions) {
61 | super(message, options);
62 | // set error name as constructor name, make it not enumerable to keep native Error behavior
63 | // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target#new.target_in_constructors
64 | // see https://github.com/adriengibrat/ts-custom-error/issues/30
65 | Object.defineProperty(this, "name", {
66 | value: new.target.name,
67 | enumerable: false,
68 | configurable: true,
69 | });
70 | // fix the extended error prototype chain
71 | // because typescript __extends implementation can't
72 | // see https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
73 | fixProto(this, new.target.prototype);
74 | // try to remove contructor from stack trace
75 | fixStack(this);
76 | }
77 | }
78 |
79 | interface ICoreKitError extends CustomError {
80 | name: string;
81 | code: number;
82 | message: string;
83 | toString(): string;
84 | }
85 |
86 | abstract class AbstractCoreKitError extends CustomError implements ICoreKitError {
87 | code: number;
88 |
89 | message: string;
90 |
91 | public constructor(code?: number, message?: string) {
92 | // takes care of stack and proto
93 | super(message);
94 |
95 | this.code = code;
96 | this.message = message || "";
97 | // Set name explicitly as minification can mangle class names
98 | Object.defineProperty(this, "name", { value: "TkeyError" });
99 | }
100 |
101 | toJSON(): ICoreKitError {
102 | return {
103 | name: this.name,
104 | code: this.code,
105 | message: this.message,
106 | };
107 | }
108 |
109 | toString(): string {
110 | return JSON.stringify(this.toJSON());
111 | }
112 | }
113 |
114 | /**
115 | * CoreKitError, extension for Error using CustomError
116 | *
117 | * Usage:
118 | * 1. throw CoreKitError.factorKeyNotPresent("Required factor key missing in the operation."); // Use a predefined method to throw a common error
119 | * 2. throw CoreKitError.fromCode(1001); // Throw an error using a code for a common error
120 | * 3. throw new CoreKitError(1102, "'tkey' instance has not been initialized."); // Throw a specific error with a custom message
121 | *
122 | * Guide:
123 | * 1000 - Configuration errors
124 | * 1100 - TSS and key management errors
125 | * 1200 - Factor key and authentication errors
126 | * 1300 - Initialization and session management
127 | */
128 | class CoreKitError extends AbstractCoreKitError {
129 | protected static messages: { [key: number]: string } = {
130 | // Configuration errors
131 | 1001: "You must specify a valid eip155 chain configuration in the options.",
132 | 1002: "You must specify a web3auth clientId.",
133 | 1003: "Unsupported storage type in this UX mode.",
134 | 1004: "OAuth login is NOT supported in this UX mode.",
135 | 1005: "No valid storage option found.",
136 | 1006: "No data found in storage.",
137 | 1007: "Invalid config.",
138 |
139 | // TSS and key management errors
140 | 1101: "'tssLib' is required when running in this UX mode.",
141 | 1102: "'tkey' instance has not been initialized.",
142 | 1103: "Duplicate TSS index found. Ensure that each TSS index is unique.",
143 | 1104: "Failed to retrieve node details. Please check your network connection and try again.",
144 | 1105: "The prefetch TSS public keys exceeds the maximum allowed limit of 3.",
145 | 1106: "Invalid 'TorusLoginResponse' data provided.",
146 | 1107: "Invalid 'TorusAggregateLoginResponse' data provided.",
147 | 1108: "Unsupported method type encountered in redirect result.",
148 | 1109: "OAuthKey not present in state.",
149 | 1110: "TSS Share Type (Index) not present in state when getting current factor key.",
150 | 1111: "'tssPubKey' or 'torusNodeTSSEndpoints' are missing.",
151 | 1112: "No active session found.",
152 | 1113: "tssNonces not present in metadata when getting tss nonce.",
153 | 1114: "A TSS key cannot be imported for an existing user who already has a key configured.",
154 |
155 | // Factor key and authentication errors
156 | 1201: "factorKey not present in state when required.",
157 | 1202: "A factor with the same key already exists.",
158 | 1203: "MFA is already enabled.",
159 | 1204: "Cannot delete the last remaining factor as at least one factor is required.",
160 | 1205: "The factor currently in use cannot be deleted.",
161 | 1206: "User is not logged in.",
162 | 1207: "Provided factor key is invalid.",
163 | 1208: "'factorEncs' mpt [resemt].",
164 | 1209: "No metadata found for the provided factor key. Consider resetting your account if this error persists.",
165 | 1210: "The new share index is not valid. It must be one of the valid share indices.",
166 | 1211: "The maximum number of allowable factors (10) has been reached.",
167 | 1212: "No metadata share found in the current polynomial.",
168 | 1213: "No signatures found.",
169 | 1214: "Factor public keys not present",
170 |
171 | // Initialization and session management
172 | 1301: "The 'CommitChanges' method must be called before enabling MFA.",
173 | 1302: "The MPC Core Kit is not initialized. Please ensure you call the 'init()' method to initialize the kit properly before attempting any operations.",
174 | };
175 |
176 | public constructor(code: number, message: string) {
177 | super(code, message);
178 | Object.defineProperty(this, "name", { value: "CoreKitError" });
179 | }
180 |
181 | public static fromCode(code: number, extraMessage = ""): ICoreKitError {
182 | return new CoreKitError(code, `${CoreKitError.messages[code]} ${extraMessage}`);
183 | }
184 |
185 | public static default(extraMessage = ""): ICoreKitError {
186 | return new CoreKitError(1000, `${CoreKitError.messages[1000]} ${extraMessage}`);
187 | }
188 |
189 | // Configuration errors
190 | public static chainConfigInvalid(extraMessage = ""): ICoreKitError {
191 | return CoreKitError.fromCode(1001, extraMessage);
192 | }
193 |
194 | public static clientIdInvalid(extraMessage = ""): ICoreKitError {
195 | return CoreKitError.fromCode(1002, extraMessage);
196 | }
197 |
198 | public static storageTypeUnsupported(extraMessage = ""): ICoreKitError {
199 | return CoreKitError.fromCode(1003, extraMessage);
200 | }
201 |
202 | public static oauthLoginUnsupported(extraMessage = ""): ICoreKitError {
203 | return CoreKitError.fromCode(1004, extraMessage);
204 | }
205 |
206 | public static noValidStorageOptionFound(extraMessage = ""): ICoreKitError {
207 | return CoreKitError.fromCode(1005, extraMessage);
208 | }
209 |
210 | public static noDataFoundInStorage(extraMessage = ""): ICoreKitError {
211 | return CoreKitError.fromCode(1006, extraMessage);
212 | }
213 |
214 | public static invalidConfig(extraMessage = ""): ICoreKitError {
215 | return CoreKitError.fromCode(1007, extraMessage);
216 | }
217 |
218 | // TSS and key management errors
219 | public static tssLibRequired(extraMessage = ""): ICoreKitError {
220 | return CoreKitError.fromCode(1101, extraMessage);
221 | }
222 |
223 | public static tkeyInstanceUninitialized(extraMessage = ""): ICoreKitError {
224 | return CoreKitError.fromCode(1102, extraMessage);
225 | }
226 |
227 | public static duplicateTssIndex(extraMessage = ""): ICoreKitError {
228 | return CoreKitError.fromCode(1103, extraMessage);
229 | }
230 |
231 | public static nodeDetailsRetrievalFailed(extraMessage = ""): ICoreKitError {
232 | return CoreKitError.fromCode(1104, extraMessage);
233 | }
234 |
235 | public static prefetchValueExceeded(extraMessage = ""): ICoreKitError {
236 | return CoreKitError.fromCode(1105, extraMessage);
237 | }
238 |
239 | public static invalidTorusLoginResponse(extraMessage = ""): ICoreKitError {
240 | return CoreKitError.fromCode(1106, extraMessage);
241 | }
242 |
243 | public static invalidTorusAggregateLoginResponse(extraMessage = ""): ICoreKitError {
244 | return CoreKitError.fromCode(1107, extraMessage);
245 | }
246 |
247 | public static unsupportedRedirectMethod(extraMessage = ""): ICoreKitError {
248 | return CoreKitError.fromCode(1108, extraMessage);
249 | }
250 |
251 | public static postBoxKeyMissing(extraMessage = ""): ICoreKitError {
252 | return CoreKitError.fromCode(1109, extraMessage);
253 | }
254 |
255 | public static tssShareTypeIndexMissing(extraMessage = ""): ICoreKitError {
256 | return CoreKitError.fromCode(1110, extraMessage);
257 | }
258 |
259 | public static tssPublicKeyOrEndpointsMissing(extraMessage = ""): ICoreKitError {
260 | return CoreKitError.fromCode(1111, extraMessage);
261 | }
262 |
263 | public static activeSessionNotFound(extraMessage = ""): ICoreKitError {
264 | return CoreKitError.fromCode(1112, extraMessage);
265 | }
266 |
267 | public static tssNoncesMissing(extraMessage = ""): ICoreKitError {
268 | return CoreKitError.fromCode(1113, extraMessage);
269 | }
270 |
271 | public static tssKeyImportNotAllowed(extraMessage = ""): ICoreKitError {
272 | return CoreKitError.fromCode(1114, extraMessage);
273 | }
274 |
275 | // Factor key and authentication errors
276 | public static factorKeyNotPresent(extraMessage = ""): ICoreKitError {
277 | return CoreKitError.fromCode(1201, extraMessage);
278 | }
279 |
280 | public static factorKeyAlreadyExists(extraMessage = ""): ICoreKitError {
281 | return CoreKitError.fromCode(1202, extraMessage);
282 | }
283 |
284 | public static mfaAlreadyEnabled(extraMessage = ""): ICoreKitError {
285 | return CoreKitError.fromCode(1203, extraMessage);
286 | }
287 |
288 | public static cannotDeleteLastFactor(extraMessage = ""): ICoreKitError {
289 | return CoreKitError.fromCode(1204, extraMessage);
290 | }
291 |
292 | public static factorInUseCannotBeDeleted(extraMessage = ""): ICoreKitError {
293 | return CoreKitError.fromCode(1205, extraMessage);
294 | }
295 |
296 | public static userNotLoggedIn(extraMessage = ""): ICoreKitError {
297 | return CoreKitError.fromCode(1206, extraMessage);
298 | }
299 |
300 | public static providedFactorKeyInvalid(extraMessage = ""): ICoreKitError {
301 | return CoreKitError.fromCode(1207, extraMessage);
302 | }
303 |
304 | public static factorEncsMissing(extraMessage = ""): ICoreKitError {
305 | return CoreKitError.fromCode(1208, extraMessage);
306 | }
307 |
308 | public static noMetadataFound(extraMessage = ""): ICoreKitError {
309 | return CoreKitError.fromCode(1209, extraMessage);
310 | }
311 |
312 | public static newShareIndexInvalid(extraMessage = ""): ICoreKitError {
313 | return CoreKitError.fromCode(1210, extraMessage);
314 | }
315 |
316 | public static maximumFactorsReached(extraMessage = ""): ICoreKitError {
317 | return CoreKitError.fromCode(1211, extraMessage);
318 | }
319 |
320 | public static noMetadataShareFound(extraMessage = ""): ICoreKitError {
321 | return CoreKitError.fromCode(1212, extraMessage);
322 | }
323 |
324 | public static signaturesNotPresent(extraMessage = ""): ICoreKitError {
325 | return CoreKitError.fromCode(1213, extraMessage);
326 | }
327 |
328 | public static factorPubsMissing(extraMessage = ""): ICoreKitError {
329 | return CoreKitError.fromCode(1214, extraMessage);
330 | }
331 |
332 | // Initialization and session management
333 | public static commitChangesBeforeMFA(extraMessage = ""): ICoreKitError {
334 | return CoreKitError.fromCode(1301, extraMessage);
335 | }
336 |
337 | public static mpcCoreKitNotInitialized(extraMessage = ""): ICoreKitError {
338 | return CoreKitError.fromCode(1302, extraMessage);
339 | }
340 | }
341 |
342 | export default CoreKitError;
343 |
--------------------------------------------------------------------------------
/src/helper/factorSerialization.ts:
--------------------------------------------------------------------------------
1 | import { ShareSerializationModule } from "@tkey/share-serialization";
2 | import BN from "bn.js";
3 |
4 | /**
5 | * Converts a mnemonic to a BN.
6 | * @param shareMnemonic - The mnemonic to convert.
7 | * @returns A BN respective to your mnemonic
8 | */
9 | export function mnemonicToKey(shareMnemonic: string): string {
10 | const factorKey = ShareSerializationModule.deserializeMnemonic(shareMnemonic);
11 | return factorKey.toString("hex");
12 | }
13 |
14 | /**
15 | * Converts a BN to a mnemonic.
16 | * @param shareBN - The BN to convert.
17 | * @returns A mnemonic respective to your BN
18 | */
19 | export function keyToMnemonic(shareHex: string): string {
20 | const shareBN = new BN(shareHex, "hex");
21 | const mnemonic = ShareSerializationModule.serializeMnemonic(shareBN);
22 | return mnemonic;
23 | }
24 |
--------------------------------------------------------------------------------
/src/helper/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./browserStorage";
2 | export * from "./factorSerialization";
3 | export * from "./securityQuestion";
4 |
--------------------------------------------------------------------------------
/src/helper/securityQuestion.ts:
--------------------------------------------------------------------------------
1 | import { Point, secp256k1, StringifiedType } from "@tkey/common-types";
2 | import { factorKeyCurve, getPubKeyPoint } from "@tkey/tss";
3 | import { keccak256 } from "@toruslabs/torus.js";
4 | import BN from "bn.js";
5 |
6 | import { FactorKeyTypeShareDescription, TssShareType, VALID_SHARE_INDICES } from "../constants";
7 | import type { Web3AuthMPCCoreKit } from "../mpcCoreKit";
8 |
9 | export class TssSecurityQuestionStore {
10 | shareIndex: string;
11 |
12 | factorPublicKey: string;
13 |
14 | question: string;
15 |
16 | constructor(shareIndex: string, factorPublicKey: string, question: string) {
17 | this.shareIndex = shareIndex;
18 | this.factorPublicKey = factorPublicKey;
19 | this.question = question;
20 | }
21 |
22 | static fromJSON(json: StringifiedType) {
23 | const { shareIndex, factorPublicKey, question } = json;
24 | return new TssSecurityQuestionStore(shareIndex, factorPublicKey, question);
25 | }
26 |
27 | toJSON(): StringifiedType {
28 | return {
29 | shareIndex: this.shareIndex,
30 | factorPublicKey: this.factorPublicKey,
31 | question: this.question,
32 | };
33 | }
34 | }
35 |
36 | export interface setSecurityQuestionParams {
37 | mpcCoreKit: Web3AuthMPCCoreKit;
38 | question: string;
39 | answer: string;
40 | shareType?: TssShareType;
41 | description?: Record;
42 | tssIndex?: TssShareType;
43 | }
44 |
45 | export interface changeSecurityQuestionParams {
46 | mpcCoreKit: Web3AuthMPCCoreKit;
47 | newQuestion: string;
48 | newAnswer: string;
49 | answer: string;
50 | }
51 |
52 | export class TssSecurityQuestion {
53 | storeDomainName = "tssSecurityQuestion";
54 |
55 | async setSecurityQuestion(params: setSecurityQuestionParams): Promise {
56 | const { mpcCoreKit, question, answer, description } = params;
57 | let { shareType } = params;
58 |
59 | if (!mpcCoreKit.tKey) {
60 | throw new Error("Tkey not initialized, call init first.");
61 | }
62 | if (!question || !answer) {
63 | throw new Error("question and answer are required");
64 | }
65 | const domainKey = `${this.storeDomainName}:${params.mpcCoreKit.tKey.tssTag}`;
66 |
67 | // default using recovery index
68 | if (!shareType) {
69 | shareType = TssShareType.RECOVERY;
70 | } else if (!VALID_SHARE_INDICES.includes(shareType)) {
71 | throw new Error(`invalid share type: must be one of ${VALID_SHARE_INDICES}`);
72 | }
73 | // Check for existing security question
74 | const tkey = mpcCoreKit.tKey;
75 | const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey) as StringifiedType;
76 | if (storeDomain && storeDomain.question) {
77 | throw new Error("Security question already exists");
78 | }
79 |
80 | // const pubKey = Point.fromTkeyPoint(mpcCoreKit.tKey.getTSSPub()).toBufferSEC1(true).toString("hex");
81 | const pubKey = tkey.getKeyDetails().pubKey.toSEC1(secp256k1, true).toString("hex") + tkey.tssTag;
82 | let hash = keccak256(Buffer.from(answer + pubKey, "utf8"));
83 | hash = hash.startsWith("0x") ? hash.slice(2) : hash;
84 | const factorKeyBN = new BN(hash, "hex");
85 |
86 | const descriptionFinal = {
87 | question,
88 | ...description,
89 | };
90 |
91 | await mpcCoreKit.createFactor({
92 | factorKey: factorKeyBN,
93 | shareType,
94 | shareDescription: FactorKeyTypeShareDescription.SecurityQuestions,
95 | additionalMetadata: descriptionFinal,
96 | });
97 | // set store domain
98 | const tkeyPt = getPubKeyPoint(factorKeyBN, factorKeyCurve);
99 | const factorPub = tkeyPt.toSEC1(factorKeyCurve, true).toString("hex");
100 | const storeData = new TssSecurityQuestionStore(shareType.toString(), factorPub, question);
101 | tkey.metadata.setGeneralStoreDomain(domainKey, storeData.toJSON());
102 |
103 | // check for auto commit
104 | if (!tkey.manualSync) await tkey._syncShareMetadata();
105 |
106 | return factorKeyBN.toString("hex").padStart(64, "0");
107 | }
108 |
109 | async changeSecurityQuestion(params: changeSecurityQuestionParams) {
110 | const { mpcCoreKit, newQuestion, newAnswer, answer } = params;
111 | if (!newQuestion || !newAnswer || !answer) {
112 | throw new Error("question and answer are required");
113 | }
114 | // Check for existing security question
115 | const tkey = mpcCoreKit.tKey;
116 | // const pubKey = Point.fromTkeyPoint(mpcCoreKit.tKey.getTSSPub()).toBufferSEC1(true).toString("hex");
117 | const pubKey = tkey.getKeyDetails().pubKey.toSEC1(secp256k1, true).toString("hex") + tkey.tssTag;
118 |
119 | const domainKey = `${this.storeDomainName}:${params.mpcCoreKit.tKey.tssTag}`;
120 | const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey) as StringifiedType;
121 | if (!storeDomain || !storeDomain.question) {
122 | throw new Error("Security question does not exists");
123 | }
124 |
125 | const store = TssSecurityQuestionStore.fromJSON(storeDomain);
126 | const preHash = answer + pubKey;
127 | let hash = keccak256(Buffer.from(preHash, "utf8"));
128 | hash = hash.startsWith("0x") ? hash.slice(2) : hash;
129 | const factorKeyBN = new BN(hash, "hex");
130 | const factorKeyPt = getPubKeyPoint(factorKeyBN, factorKeyCurve);
131 | if (factorKeyPt.toSEC1(factorKeyCurve, true).toString("hex") !== store.factorPublicKey) {
132 | throw new Error("Invalid answer");
133 | }
134 |
135 | // create new factor key
136 | const prenewHash = newAnswer + pubKey;
137 | let newHash = keccak256(Buffer.from(prenewHash, "utf8"));
138 | newHash = newHash.startsWith("0x") ? newHash.slice(2) : newHash;
139 | const newAnswerBN = new BN(newHash, "hex");
140 | const newFactorPt = Point.fromScalar(newAnswerBN, factorKeyCurve);
141 | await mpcCoreKit.createFactor({
142 | factorKey: newAnswerBN,
143 | shareType: parseInt(store.shareIndex) as TssShareType,
144 | shareDescription: FactorKeyTypeShareDescription.SecurityQuestions,
145 | });
146 |
147 | // update mpcCoreKit state to use new factor key during change password if mpc factor key is security question factor
148 | if (mpcCoreKit.state.factorKey.eq(factorKeyBN)) {
149 | await mpcCoreKit.inputFactorKey(newAnswerBN);
150 | }
151 | // delete after create factor to prevent last key issue
152 | // delete old factor key and device share
153 | await mpcCoreKit.deleteFactor(factorKeyPt, factorKeyBN);
154 |
155 | store.factorPublicKey = newFactorPt.toSEC1(factorKeyCurve, true).toString("hex");
156 | store.question = newQuestion;
157 | tkey.metadata.setGeneralStoreDomain(domainKey, store.toJSON());
158 |
159 | // check for auto commit
160 | if (!tkey.manualSync) await tkey._syncShareMetadata();
161 | }
162 |
163 | // Should we check with answer before deleting?
164 | async deleteSecurityQuestion(mpcCoreKit: Web3AuthMPCCoreKit, deleteFactorKey = true) {
165 | if (!mpcCoreKit.tKey) {
166 | throw new Error("Tkey not initialized, call init first.");
167 | }
168 |
169 | const domainKey = `${this.storeDomainName}:${mpcCoreKit.tKey.tssTag}`;
170 | const tkey = mpcCoreKit.tKey;
171 | if (deleteFactorKey) {
172 | const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey) as StringifiedType;
173 | if (!storeDomain || !storeDomain.question) {
174 | throw new Error("Security question does not exists");
175 | }
176 |
177 | const store = TssSecurityQuestionStore.fromJSON(storeDomain);
178 | if (store.factorPublicKey) {
179 | await mpcCoreKit.deleteFactor(Point.fromSEC1(factorKeyCurve, store.factorPublicKey));
180 | }
181 | }
182 | tkey.metadata.deleteGeneralStoreDomain(domainKey);
183 | // check for auto commit
184 | if (!tkey.manualSync) await tkey._syncShareMetadata();
185 | }
186 |
187 | async recoverFactor(mpcCoreKit: Web3AuthMPCCoreKit, answer: string): Promise {
188 | if (!mpcCoreKit.tKey) {
189 | throw new Error("Tkey not initialized, call init first.");
190 | }
191 | if (!answer) {
192 | throw new Error("question and answer are required");
193 | }
194 |
195 | const tkey = mpcCoreKit.tKey;
196 |
197 | const domainKey = `${this.storeDomainName}:${mpcCoreKit.tKey.tssTag}`;
198 | const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey) as StringifiedType;
199 | if (!storeDomain || !storeDomain.question) {
200 | throw new Error("Security question does not exists");
201 | }
202 |
203 | const store = TssSecurityQuestionStore.fromJSON(storeDomain);
204 |
205 | // const pubKey = Point.fromTkeyPoint(mpcCoreKit.tKey.getTSSPub()).toBufferSEC1(true).toString("hex");
206 | const pubKey = tkey.getKeyDetails().pubKey.toSEC1(secp256k1, true).toString("hex") + tkey.tssTag;
207 |
208 | let hash = keccak256(Buffer.from(answer + pubKey, "utf8"));
209 | hash = hash.startsWith("0x") ? hash.slice(2) : hash;
210 | const factorKeyBN = new BN(hash, "hex");
211 | const factorKeyPt = Point.fromScalar(factorKeyBN, factorKeyCurve);
212 |
213 | if (factorKeyPt.toSEC1(factorKeyCurve, true).toString("hex") !== store.factorPublicKey) {
214 | throw new Error("Invalid answer");
215 | }
216 |
217 | return hash;
218 | }
219 |
220 | getQuestion(mpcCoreKit: Web3AuthMPCCoreKit): string {
221 | if (!mpcCoreKit.tKey) {
222 | throw new Error("Tkey not initialized, call init first.");
223 | }
224 | const tkey = mpcCoreKit.tKey;
225 |
226 | const domainKey = `${this.storeDomainName}:${mpcCoreKit.tKey.tssTag}`;
227 | const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey) as StringifiedType;
228 | if (!storeDomain || !storeDomain.question) {
229 | throw new Error("Security question does not exists");
230 | }
231 |
232 | const store = TssSecurityQuestionStore.fromJSON(storeDomain);
233 | return store.question;
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./constants";
2 | export * from "./helper";
3 | export * from "./interfaces";
4 | export * from "./mpcCoreKit";
5 | export * from "./utils";
6 | export { factorKeyCurve } from "@tkey/tss";
7 |
--------------------------------------------------------------------------------
/src/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types";
2 | import { TKeyTSS } from "@tkey/tss";
3 | import type {
4 | AGGREGATE_VERIFIER_TYPE,
5 | ExtraParams,
6 | LoginWindowResponse,
7 | PasskeyExtraParams,
8 | SubVerifierDetails,
9 | TorusVerifierResponse,
10 | UX_MODE_TYPE,
11 | } from "@toruslabs/customauth";
12 | import { Client } from "@toruslabs/tss-client";
13 | // TODO: move the types to a base class for both dkls and frost in future
14 | import type { tssLib as TssDklsLib } from "@toruslabs/tss-dkls-lib";
15 | import type { tssLib as TssFrostLib } from "@toruslabs/tss-frost-lib";
16 | import BN from "bn.js";
17 |
18 | import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants";
19 |
20 | export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native";
21 |
22 | export type V3TSSLibType = { keyType: string; lib: unknown };
23 |
24 | export type V4TSSLibType = typeof TssFrostLib | typeof TssDklsLib;
25 | export type TssLibType = V4TSSLibType | V3TSSLibType;
26 | export interface IStorage {
27 | getItem(key: string): string | null;
28 | setItem(key: string, value: string): void;
29 | }
30 | export interface IAsyncStorage {
31 | async?: boolean;
32 | getItem(key: string): Promise;
33 | setItem(key: string, value: string): Promise;
34 | }
35 |
36 | export type SupportedStorageType = "local" | "session" | "memory" | IStorage;
37 |
38 | export interface InitParams {
39 | /**
40 | * @defaultValue `true`
41 | * handle the redirect result during init()
42 | */
43 | handleRedirectResult: boolean;
44 | /**
45 | * @defaultValue `true`
46 | * rehydrate the session during init()
47 | */
48 | rehydrate?: boolean;
49 | }
50 |
51 | export interface BaseLoginParams {
52 | // offset in seconds
53 | serverTimeOffset?: number;
54 | }
55 |
56 | export interface SubVerifierDetailsParams extends BaseLoginParams {
57 | subVerifierDetails: SubVerifierDetails;
58 | }
59 |
60 | export interface AggregateVerifierLoginParams extends BaseLoginParams {
61 | aggregateVerifierIdentifier: string;
62 | subVerifierDetailsArray: SubVerifierDetails[];
63 | aggregateVerifierType?: AGGREGATE_VERIFIER_TYPE;
64 | }
65 |
66 | export interface IFactorKey {
67 | factorKey: BN;
68 | shareType: TssShareType;
69 | }
70 |
71 | export enum COREKIT_STATUS {
72 | NOT_INITIALIZED = "NOT_INITIALIZED",
73 | INITIALIZED = "INITIALIZED",
74 | REQUIRED_SHARE = "REQUIRED_SHARE",
75 | LOGGED_IN = "LOGGED_IN",
76 | }
77 |
78 | export type MPCKeyDetails = {
79 | metadataPubKey: TkeyPoint;
80 | threshold: number;
81 | requiredFactors: number;
82 | totalFactors: number;
83 | shareDescriptions: ShareDescriptionMap;
84 | keyType: KeyType;
85 | tssPubKey?: TkeyPoint;
86 | };
87 |
88 | export type OAuthLoginParams = (SubVerifierDetailsParams | AggregateVerifierLoginParams) & {
89 | /**
90 | * Key to import key into Tss during first time login.
91 | */
92 | importTssKey?: string;
93 |
94 | /**
95 | * For new users, use SFA key if user was registered with SFA before.
96 | * Useful when you created the user with SFA before and now want to convert it to TSS.
97 | */
98 | registerExistingSFAKey?: boolean;
99 | };
100 | export type UserInfo = TorusVerifierResponse & LoginWindowResponse;
101 |
102 | export interface EnableMFAParams {
103 | /**
104 | * A BN used for encrypting your Device/ Recovery TSS Key Share. You can generate it using `generateFactorKey()` function or use an existing one.
105 | */
106 | factorKey?: BN;
107 | /**
108 | * Setting the Description of Share - Security Questions, Device Share, Seed Phrase, Password Share, Social Share, Other. Default is Other.
109 | */
110 | shareDescription?: FactorKeyTypeShareDescription;
111 | /**
112 | * Additional metadata information you want to be stored alongside this factor for easy identification.
113 | */
114 | additionalMetadata?: Record;
115 | }
116 |
117 | export interface CreateFactorParams extends EnableMFAParams {
118 | /**
119 | * Setting the Type of Share - Device or Recovery.
120 | **/
121 | shareType: TssShareType;
122 | }
123 |
124 | export interface JWTLoginParams {
125 | /**
126 | * Name of the verifier created on Web3Auth Dashboard. In case of Aggregate Verifier, the name of the top level aggregate verifier.
127 | */
128 | verifier: string;
129 |
130 | /**
131 | * Unique Identifier for the User. The verifier identifier field set for the verifier/ sub verifier. E.g. "sub" field in your on jwt id token.
132 | */
133 | verifierId: string;
134 |
135 | /**
136 | * The idToken received from the Auth Provider.
137 | */
138 | idToken: string;
139 |
140 | /**
141 | * Name of the sub verifier in case of aggregate verifier setup. This field should only be provided in case of an aggregate verifier.
142 | */
143 | subVerifier?: string;
144 |
145 | /**
146 | * Extra verifier params in case of a WebAuthn verifier type.
147 | */
148 | extraVerifierParams?: PasskeyExtraParams;
149 |
150 | /**
151 | * Any additional parameter (key value pair) you'd like to pass to the login function.
152 | */
153 | additionalParams?: ExtraParams;
154 |
155 | /**
156 | * Key to import key into Tss during first time login.
157 | */
158 | importTssKey?: string;
159 |
160 | /**
161 | * For new users, use SFA key if user was registered with SFA before.
162 | * Useful when you created the user with SFA before and now want to convert it to TSS.
163 | */
164 | registerExistingSFAKey?: boolean;
165 |
166 | /**
167 | * Number of TSS public keys to prefetch. For the best performance, set it to
168 | * the number of factors you want to create. Set it to 0 for an existing user.
169 | * Default is 1, maximum is 3.
170 | */
171 | prefetchTssPublicKeys?: number;
172 | }
173 |
174 | export interface Web3AuthState {
175 | postBoxKey?: string;
176 | signatures?: string[];
177 | postboxKeyNodeIndexes?: number[];
178 | userInfo?: UserInfo;
179 | tssShareIndex?: number;
180 | tssPubKey?: Buffer;
181 | accountIndex: number;
182 | factorKey?: BN;
183 | }
184 |
185 | export interface ICoreKit {
186 | /**
187 | * The tKey instance, if initialized.
188 | * TKey is the core module on which this wrapper SDK sits for easy integration.
189 | **/
190 | tKey: TKeyTSS | null;
191 |
192 | /**
193 | * Signatures generated from the OAuth Login.
194 | **/
195 | signatures: string[] | null;
196 |
197 | /**
198 | * Status of the current MPC Core Kit Instance
199 | **/
200 | status: COREKIT_STATUS;
201 |
202 | /**
203 | * The current sdk state.
204 | */
205 | state: Web3AuthState;
206 |
207 | /**
208 | * The current session id.
209 | */
210 | sessionId: string;
211 |
212 | /**
213 | * The function used to initailise the state of MPCCoreKit
214 | * Also is useful to resume an existing session.
215 | * @param initParams - Contains flag for handleRedirectResult. Default is true.
216 | */
217 | init(initParams?: InitParams): Promise;
218 |
219 | /**
220 | * Login using OAuth flow and initialize all relevant components.
221 | * @param loginParams - Parameters for OAuth-based Login.
222 | */
223 | loginWithOAuth(loginParams: OAuthLoginParams): Promise;
224 |
225 | /**
226 | * Login using JWT Token and initialize all relevant components.
227 | * @param loginParams - Parameters for JWT-based Login.
228 | */
229 | loginWithJWT(loginParams: JWTLoginParams): Promise;
230 |
231 | /**
232 | * Enable MFA for the user. Deletes the Cloud factor and generates a new
233 | * factor key and a backup factor key. Recommended for Non Custodial Flow.
234 | * Stores the factor key in browser storage and returns the backup factor key.
235 | *
236 | * ** NOTE before enableMFA, you will need to commitChanges if manualSync is true.
237 | *
238 | * @param enableMFAParams - Parameters for recovery factor for MFA.
239 | * @param recoveryFactor - Default is true. If false, recovery factor will NOT be created.
240 | * @returns The backup factor key if if recoveryFacort is true else empty string.
241 | */
242 | enableMFA(enableMFAParams: EnableMFAParams, recoveryFactor?: boolean): Promise;
243 |
244 | /**
245 | * Second step for login where the user inputs their factor key.
246 | * @param factorKey - A BN used for encrypting your Device/ Recovery TSS Key
247 | * Share. You can generate it using `generateFactorKey()` function or use an
248 | * existing one.
249 | */
250 | inputFactorKey(factorKey: BN): Promise;
251 |
252 | /**
253 | * Returns the current Factor Key and TssShareType in MPC Core Kit State
254 | **/
255 | getCurrentFactorKey(): IFactorKey;
256 |
257 | /**
258 | * Creates a new factor for authentication. Generates and returns a new factor
259 | * key if no factor key is provided in `params`.
260 | * @param createFactorParams - Parameters for creating a new factor.
261 | * @returns The factor key.
262 | */
263 | createFactor(createFactorParams: CreateFactorParams): Promise;
264 |
265 | /**
266 | * Deletes the factor identified by the given public key, including all
267 | * associated metadata.
268 | * @param factorPub - The public key of the factor to delete.
269 | */
270 | deleteFactor(factorPub: TkeyPoint): Promise;
271 |
272 | /**
273 | * Logs out the user, terminating the session.
274 | */
275 | logout(): Promise;
276 |
277 | /**
278 | * Get user information provided by the OAuth provider.
279 | */
280 | getUserInfo(): UserInfo;
281 |
282 | /**
283 | * Get information about how the keys of the user is managed according to the information in the metadata server.
284 | */
285 | getKeyDetails(): MPCKeyDetails;
286 |
287 | /**
288 | * Commit the changes made to the user's account when in manual sync mode.
289 | */
290 | commitChanges(): Promise;
291 |
292 | /**
293 | * WARNING: Use with caution. This will export the private signing key.
294 | *
295 | * Exports the private key scalar for the current account index.
296 | *
297 | * For keytype ed25519, consider using _UNSAFE_exportTssEd25519Seed.
298 | */
299 | _UNSAFE_exportTssKey(): Promise;
300 |
301 | /**
302 | * WARNING: Use with caution. This will export the private signing key.
303 | *
304 | * Attempts to export the ed25519 private key seed. Only works if import key
305 | * flow has been used.
306 | */
307 | _UNSAFE_exportTssEd25519Seed(): Promise;
308 | }
309 |
310 | export type WEB3AUTH_NETWORK_TYPE = (typeof WEB3AUTH_NETWORK)[keyof typeof WEB3AUTH_NETWORK];
311 |
312 | export type USER_PATH_TYPE = (typeof USER_PATH)[keyof typeof USER_PATH];
313 |
314 | export interface Web3AuthOptions {
315 | /**
316 | * The Web3Auth Client ID for your application. Find one at https://dashboard.web3auth.io
317 | */
318 | web3AuthClientId: string;
319 |
320 | /**
321 | * The threshold signing library to use.
322 | */
323 | tssLib: TssLibType;
324 |
325 | /**
326 | * @defaultValue `false`
327 | */
328 | manualSync?: boolean;
329 |
330 | /**
331 | * @defaultValue `${window.location.origin}/serviceworker`
332 | */
333 | baseUrl?: string;
334 |
335 | /**
336 | *
337 | * @defaultValue `'sapphire_mainnet'`
338 | */
339 | web3AuthNetwork?: WEB3AUTH_NETWORK_TYPE;
340 |
341 | /**
342 | * storage for mpc-core-kit's local state.
343 | * storage replaces previous' storageKey and asyncStorage options.
344 | *
345 | * Migration from storageKey and asyncStorage to storage guide.
346 | *
347 | * For StorageKey, please replace
348 | * - undefined with localStorage
349 | * - "local" with localStorage
350 | * - "session" with sessionStorage
351 | * - "memory" with new MemoryStorage()
352 | *
353 | * For asyncStorage, provide instance of IAsyncStorage.
354 | *
355 | */
356 | storage: IAsyncStorage | IStorage;
357 |
358 | /**
359 | * @defaultValue false
360 | * disable session manager creation
361 | * signatures from web3auth newtorks will still expired after sessionTime if session manager is disabled
362 | */
363 | disableSessionManager?: boolean;
364 |
365 | /**
366 | * @defaultValue 86400
367 | */
368 | sessionTime?: number;
369 |
370 | /**
371 | * @defaultValue `'POPUP'`
372 | */
373 | uxMode?: CoreKitMode;
374 |
375 | /**
376 | * @defaultValue `false`
377 | * enables logging of the internal packages.
378 | */
379 | enableLogging?: boolean;
380 |
381 | /**
382 | * This option is used to specify the url path where user will be
383 | * redirected after login. Redirect Uri for OAuth is baseUrl/redirectPathName.
384 | *
385 | *
386 | * @defaultValue `"redirect"`
387 | *
388 | * @remarks
389 | * At verifier's interface (where you obtain client id), please use baseUrl/redirectPathName
390 | * as the redirect_uri
391 | *
392 | * Torus Direct SDK installs a service worker relative to baseUrl to capture
393 | * the auth redirect at `redirectPathName` path.
394 | *
395 | * For ex: While using serviceworker if `baseUrl` is "http://localhost:3000/serviceworker" and
396 | * `redirectPathName` is 'redirect' (which is default)
397 | * then user will be redirected to http://localhost:3000/serviceworker/redirect page after login
398 | * where service worker will capture the results and send it back to original window where login
399 | * was initiated.
400 | *
401 | * For browsers where service workers are not supported or if you wish to not use
402 | * service workers,create and serve redirect page (i.e redirect.html file which is
403 | * available in serviceworker folder of this package)
404 | *
405 | * If you are using redirect uxMode, you can get the results directly on your `redirectPathName`
406 | * path using `getRedirectResult` function.
407 | *
408 | * For ex: if baseUrl is "http://localhost:3000" and `redirectPathName` is 'auth'
409 | * then user will be redirected to http://localhost:3000/auth page after login
410 | * where you can get login result by calling `getRedirectResult` on redirected page mount.
411 | *
412 | * Please refer to examples https://github.com/torusresearch/customauth/tree/master/examples
413 | * for more understanding.
414 | *
415 | */
416 | redirectPathName?: string;
417 |
418 | /**
419 | * @defaultValue `false`
420 | * Disables the cloud factor key, enabling the one key semi custodial flow.
421 | * Recommended for Non Custodial Flow.
422 | */
423 | disableHashedFactorKey?: boolean;
424 |
425 | /**
426 | * @defaultValue `Web3AuthOptions.web3AuthClientId`
427 | * Overwrites the default value ( clientId ) used as nonce for hashing the hash factor key.
428 | *
429 | * If you want to aggregate the mfa status of client id 1 and client id 2 apps
430 | * set hashedFactorNonce to some common clientID, which can be either client id 1 or client id 2 or any other unique string
431 | * #PR 72
432 | * Do not use this unless you know what you are doing.
433 | */
434 | hashedFactorNonce?: string;
435 |
436 | serverTimeOffset?: number;
437 |
438 | /**
439 | * Set this flag to false to generate keys on client side
440 | * by default keys are generated on using dkg protocol on a distributed network
441 | * Note: This option is not supported for ed25519 key type
442 | * @defaultValue `true`
443 | */
444 | useDKG?: boolean;
445 |
446 | /**
447 | * @defaultValue `false` for secp256k1 and `true` for ed25519
448 | * Set this flag to true to use the client generated key for signing
449 | * Note: This option is set to true for ed25519 key type by default to ensure ed25519 mpc key seed exportablity.
450 | * The seed thn can be used for importing user's key other wallets like phantom etc
451 | * If you set this flag to false for ed25519 key type, you will not be able to export the seed for ed25519 keys and
452 | * only scalar will be exported, scalar can be used for signing outside of this sdk but not for importing the key in other wallets.
453 | */
454 | useClientGeneratedTSSKey?: boolean;
455 | }
456 |
457 | export type Web3AuthOptionsWithDefaults = Required;
458 |
459 | export interface SessionData {
460 | /**
461 | * @deprecated Use `postBoxKey` instead.
462 | */
463 | oAuthKey?: string;
464 | postBoxKey?: string;
465 | postboxKeyNodeIndexes?: number[];
466 | factorKey: string;
467 | tssShareIndex: number;
468 | tssPubKey: string;
469 | signatures: string[];
470 | userInfo: UserInfo;
471 | }
472 |
473 | export interface TkeyLocalStoreData {
474 | factorKey: string;
475 | }
476 |
477 | export interface CoreKitSigner {
478 | keyType: KeyType;
479 | sign(data: Buffer, hashed?: boolean): Promise;
480 | getPubKey(): Buffer;
481 | }
482 |
483 | export interface EthSig {
484 | v: number;
485 | r: Buffer;
486 | s: Buffer;
487 | }
488 |
489 | export interface EthereumSigner {
490 | sign: (msgHash: Buffer) => Promise;
491 | getPublic: () => Promise;
492 | }
493 |
494 | export interface Secp256k1PrecomputedClient {
495 | client: Client;
496 | serverCoeffs: Record;
497 | }
498 |
499 | export type PreSigningHookType = (params: { data: Uint8Array; hashed: boolean }) => Promise<{ success: boolean; error?: string; data?: string }>;
500 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { KeyType, Point, Point as TkeyPoint, secp256k1 } from "@tkey/common-types";
2 | import { generatePrivateBN } from "@tkey/core";
3 | import { factorKeyCurve } from "@tkey/tss";
4 | import { EllipticCurve } from "@toruslabs/elliptic-wrapper";
5 | import { safeatob } from "@toruslabs/openlogin-utils";
6 | import { keccak256 } from "@toruslabs/torus.js";
7 | import BN from "bn.js";
8 | import { eddsa as EDDSA } from "elliptic";
9 | import loglevel from "loglevel";
10 |
11 | import { DELIMITERS, SCALAR_LEN } from "./constants";
12 | import { CoreKitSigner, EthereumSigner, IAsyncStorage, IStorage } from "./interfaces";
13 |
14 | export const ed25519 = () => {
15 | return new EDDSA("ed25519");
16 | };
17 |
18 | /**
19 | * Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS.
20 | */
21 | export function randomBytes(bytesLength = 32): Uint8Array {
22 | // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
23 | const crypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : undefined;
24 |
25 | if (crypto && typeof crypto.getRandomValues === "function") {
26 | return crypto.getRandomValues(new Uint8Array(bytesLength));
27 | }
28 | throw new Error("crypto.getRandomValues must be defined");
29 | }
30 |
31 | export function generateEd25519Seed() {
32 | return Buffer.from(randomBytes(32));
33 | }
34 |
35 | export const generateFactorKey = (): { private: BN; pub: TkeyPoint } => {
36 | const keyPair = factorKeyCurve.genKeyPair();
37 | const pub = Point.fromElliptic(keyPair.getPublic());
38 | return { private: keyPair.getPrivate(), pub };
39 | };
40 |
41 | export const generateTSSEndpoints = (tssNodeEndpoints: string[], parties: number, clientIndex: number, nodeIndexes: number[]) => {
42 | const endpoints: string[] = [];
43 | const tssWSEndpoints: string[] = [];
44 | const partyIndexes: number[] = [];
45 | const nodeIndexesReturned: number[] = [];
46 |
47 | for (let i = 0; i < parties; i++) {
48 | partyIndexes.push(i);
49 | if (i === clientIndex) {
50 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
51 | endpoints.push(null as any);
52 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
53 | tssWSEndpoints.push(null as any);
54 | } else {
55 | const targetNodeIndex = nodeIndexes[i] - 1;
56 | endpoints.push(tssNodeEndpoints[targetNodeIndex]);
57 | tssWSEndpoints.push(new URL(tssNodeEndpoints[targetNodeIndex]).origin);
58 | nodeIndexesReturned.push(nodeIndexes[i]);
59 | }
60 | }
61 | return { endpoints, tssWSEndpoints, partyIndexes, nodeIndexesReturned };
62 | };
63 |
64 | export async function storageAvailable(storage: IStorage | IAsyncStorage): Promise {
65 | try {
66 | const x = "__storage_test__";
67 | const rand = Math.random().toString();
68 | await storage.setItem(x, rand);
69 |
70 | const value = await storage.getItem(rand);
71 | if (value !== rand) {
72 | throw new Error("Value mismatch");
73 | }
74 | return true;
75 | } catch (error) {
76 | return false;
77 | }
78 | }
79 |
80 | // TODO think which conversion functions to keep and how to export them.
81 |
82 | /**
83 | * Parses a JWT Token, without verifying the signature.
84 | * @param token - JWT Token
85 | * @returns Extracted JSON payload from the token
86 | */
87 | export function parseToken(token: string) {
88 | const payload = token.split(".")[1];
89 | return JSON.parse(safeatob(payload));
90 | }
91 |
92 | export const getHashedPrivateKey = (postboxKey: string, clientId: string): BN => {
93 | const uid = `${postboxKey}_${clientId}`;
94 | let hashUid = keccak256(Buffer.from(uid, "utf8"));
95 | hashUid = hashUid.replace("0x", "");
96 | return new BN(hashUid, "hex");
97 | };
98 |
99 | /**
100 | * Converts an elliptic curve scalar represented by a BN to a byte buffer in SEC1
101 | * format (i.e., padded to maximum length).
102 | * @param s - The scalar of type BN.
103 | * @returns The SEC1 encoded representation of the scalar.
104 | */
105 | export function scalarBNToBufferSEC1(s: BN): Buffer {
106 | return s.toArrayLike(Buffer, "be", SCALAR_LEN);
107 | }
108 |
109 | export interface ServerEndpoint {
110 | index: number;
111 | url: string;
112 | }
113 |
114 | export function sampleEndpoints(endpoints: ServerEndpoint[], n: number): ServerEndpoint[] {
115 | if (n > endpoints.length) {
116 | throw new Error("Invalid number of endpoints");
117 | }
118 | const shuffledEndpoints = endpoints.slice().sort(() => Math.random() - 0.5);
119 | return shuffledEndpoints.slice(0, n).sort((a, b) => a.index - b.index);
120 | }
121 |
122 | export function fraction(curve: EllipticCurve, nom: BN, denom: BN): BN {
123 | return nom.mul(denom.invm(curve.n)).umod(curve.n);
124 | }
125 |
126 | export function lagrangeCoefficient(curve: EllipticCurve, xCoords: BN[], targetCoeff: number, targetX: BN): BN {
127 | return xCoords
128 | .filter((_, i) => i !== targetCoeff)
129 | .reduce((prev, cur) => {
130 | const frac = fraction(curve, targetX.sub(cur), xCoords[targetCoeff].sub(cur));
131 | return prev.mul(frac).umod(curve.n);
132 | }, new BN(1));
133 | }
134 |
135 | export function lagrangeCoefficients(curve: EllipticCurve, xCoords: BN[] | number[], targetX: BN | number): BN[] {
136 | const xCoordsBN = xCoords.map((i) => new BN(i));
137 | const targetXBN = new BN(targetX);
138 | return xCoordsBN.map((_value, i) => lagrangeCoefficient(curve, xCoordsBN, i, targetXBN));
139 | }
140 |
141 | const SERVER_XCOORD_L1 = 1;
142 | const CLIENT_XCOORD_L1 = 2;
143 |
144 | /**
145 | * Derive share coefficients for client and servers.
146 | *
147 | * @param curve - The curve to be used.
148 | * @param serverXCoords - The source and target x-coordinates of the selected
149 | * servers.
150 | * @param targetClientXCoord - The target x-coordinate of the client.
151 | * @param sourceClientXCoord - The source x-coordinate of the client in the L1
152 | * hierarchy.
153 | * @returns - The share coefficients for the client and the servers.
154 | */
155 | export function deriveShareCoefficients(
156 | ec: EllipticCurve,
157 | serverXCoords: number[],
158 | targetClientXCoord: number,
159 | sourceClientXCoord: number = CLIENT_XCOORD_L1
160 | ): { serverCoefficients: BN[]; clientCoefficient: BN } {
161 | const l1Coefficients = lagrangeCoefficients(ec, [SERVER_XCOORD_L1, sourceClientXCoord], 0);
162 | const l2Coefficients = lagrangeCoefficients(ec, serverXCoords, 0);
163 |
164 | if (serverXCoords.includes(targetClientXCoord)) {
165 | throw new Error(`Invalid server x-coordinates: overlapping with client x-coordinate: ${serverXCoords} ${targetClientXCoord}`);
166 | }
167 |
168 | const targetCoefficients = lagrangeCoefficients(ec, [targetClientXCoord, ...serverXCoords], 0);
169 |
170 | // Derive server coefficients.
171 | const serverCoefficients = l2Coefficients.map((coeff, i) => fraction(ec, l1Coefficients[0].mul(coeff), targetCoefficients[i + 1]));
172 |
173 | // Derive client coefficient.
174 | const clientCoefficient = fraction(ec, l1Coefficients[1], targetCoefficients[0]);
175 |
176 | return {
177 | serverCoefficients,
178 | clientCoefficient,
179 | };
180 | }
181 |
182 | export function generateSessionNonce() {
183 | return keccak256(Buffer.from(generatePrivateBN().toString("hex") + Date.now(), "utf8"));
184 | }
185 |
186 | export function getSessionId(verifier: string, verifierId: string, tssTag: string, tssNonce: number, sessionNonce: string) {
187 | return `${verifier}${DELIMITERS.Delimiter1}${verifierId}${DELIMITERS.Delimiter2}${tssTag}${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}${sessionNonce}`;
188 | }
189 |
190 | export function sigToRSV(sig: Buffer) {
191 | if (sig.length !== 65) {
192 | throw new Error(`Invalid signature length: expected 65, got ${sig.length}`);
193 | }
194 |
195 | return { r: Buffer.from(sig.subarray(0, 32)), s: Buffer.from(sig.subarray(32, 64)), v: sig[64] };
196 | }
197 |
198 | export function makeEthereumSigner(kit: CoreKitSigner): EthereumSigner {
199 | if (kit.keyType !== KeyType.secp256k1) {
200 | throw new Error(`Invalid key type: expected secp256k1, got ${kit.keyType}`);
201 | }
202 | return {
203 | sign: async (msgHash: Buffer) => {
204 | const sig = await kit.sign(msgHash, true);
205 | return sigToRSV(sig);
206 | },
207 | getPublic: async () => {
208 | const pk = Point.fromSEC1(secp256k1, kit.getPubKey().toString("hex"));
209 | return pk.toSEC1(secp256k1).subarray(1);
210 | },
211 | };
212 | }
213 |
214 | export const log = loglevel.getLogger("mpc-core-kit");
215 | log.disableAll();
216 |
--------------------------------------------------------------------------------
/tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['../.eslintrc.js'],
3 | rules: {
4 | "mocha/handle-done-callback": false,
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/tests/backwardCompatible.spec.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 | import test from "node:test";
3 |
4 | import { EllipticPoint } from "@tkey/common-types";
5 | import { UX_MODE_TYPE } from "@toruslabs/customauth";
6 | import { keccak256 } from "@toruslabs/metadata-helpers";
7 | import { tssLib } from "@toruslabs/tss-dkls-lib";
8 | import BN from "bn.js";
9 | import { ec as EC } from "elliptic";
10 |
11 | import { AsyncStorage, COREKIT_STATUS, MemoryStorage, sigToRSV, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
12 | import { bufferToElliptic, mockLogin } from "./setup";
13 |
14 | type TestVariable = {
15 | web3AuthNetwork: WEB3AUTH_NETWORK_TYPE;
16 | uxMode: UX_MODE_TYPE | "nodejs";
17 | manualSync?: boolean;
18 |
19 | email: string;
20 | };
21 |
22 | const defaultTestEmail = "backwardcompatible";
23 | const variable: TestVariable[] = [
24 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", email: defaultTestEmail },
25 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, email: defaultTestEmail },
26 |
27 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", manualSync: true, email: defaultTestEmail },
28 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, manualSync: true, email: defaultTestEmail },
29 | ];
30 |
31 | const checkLogin = async (coreKitInstance: Web3AuthMPCCoreKit) => {
32 | const keyDetails = coreKitInstance.getKeyDetails();
33 | assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.LOGGED_IN);
34 | assert.strictEqual(keyDetails.requiredFactors, 0);
35 | const factorkey = coreKitInstance.getCurrentFactorKey();
36 | await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex"));
37 | };
38 |
39 | variable.forEach((testVariable) => {
40 | const { web3AuthNetwork, uxMode, manualSync, email } = testVariable;
41 |
42 | const storageInstance = new MemoryStorage();
43 | const newCoreKitInstance = () =>
44 | new Web3AuthMPCCoreKit({
45 | web3AuthClientId: "torus-key-test",
46 | web3AuthNetwork,
47 | baseUrl: "http://localhost:3000",
48 | uxMode,
49 | tssLib,
50 | storage: storageInstance,
51 | manualSync,
52 | });
53 |
54 | const coreKitInstance = newCoreKitInstance();
55 |
56 | const testNameSuffix = JSON.stringify(testVariable);
57 | test(`#Login Test with JWT + logout : ${testNameSuffix}`, async (t) => {
58 | t.after(async function () {
59 | // after all test tear down
60 | });
61 |
62 | await t.test("#Login ", async function () {
63 | // mocklogin
64 | const { idToken, parsedToken } = await mockLogin(email);
65 | await coreKitInstance.init({ handleRedirectResult: false });
66 | await coreKitInstance.loginWithJWT({
67 | verifier: "torus-test-health",
68 | verifierId: parsedToken.email,
69 | idToken,
70 | });
71 |
72 | // get key details
73 | await checkLogin(coreKitInstance);
74 |
75 | const tssPublicPoint = bufferToElliptic(coreKitInstance.getPubKey());
76 | const { metadataPubKey, tssPubKey } = coreKitInstance.getKeyDetails();
77 | assert.strictEqual(tssPublicPoint.getX().toString("hex"), "d2869f27c3e226d90b275b008f7dc67b8f4b208900a7b98ecc4e5266807d382c");
78 | assert.strictEqual(tssPublicPoint.getY().toString("hex"), "15860fd569413eb7f177e655c4bf855f37920b800235de344fdd518196becfe0");
79 |
80 | assert.strictEqual(tssPubKey.x.toString("hex"), "d2869f27c3e226d90b275b008f7dc67b8f4b208900a7b98ecc4e5266807d382c");
81 | assert.strictEqual(tssPubKey.y.toString("hex"), "15860fd569413eb7f177e655c4bf855f37920b800235de344fdd518196becfe0");
82 |
83 | assert.strictEqual(metadataPubKey.x.toString("hex"), "b3951a441f87ecea4672edc82894ac023316723cf164a93adec72b58a27a1f06");
84 | assert.strictEqual(metadataPubKey.y.toString("hex"), "3be6c118d94242a650e8aebbefcd37ebeceeb927d0ed51f3d2ba723b8fd2740b");
85 | });
86 |
87 | await t.test("#relogin ", async function () {
88 | // reload without rehydrate
89 | // await coreKitInstance.init({ rehydrate: false });
90 |
91 | // rehydrate
92 | await coreKitInstance.init({ handleRedirectResult: false });
93 | await checkLogin(coreKitInstance);
94 |
95 | // logout
96 | await coreKitInstance.logout();
97 |
98 | // reset the storage
99 | await new AsyncStorage(coreKitInstance._storageKey, storageInstance).resetStore();
100 |
101 | // rehydrate should fail
102 | await coreKitInstance.init();
103 | assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.INITIALIZED);
104 | assert.throws(() => coreKitInstance.getCurrentFactorKey());
105 |
106 | // relogin
107 | const { idToken, parsedToken } = await mockLogin(email);
108 | await coreKitInstance.loginWithJWT({
109 | verifier: "torus-test-health",
110 | verifierId: parsedToken.email,
111 | idToken,
112 | });
113 |
114 | // get key details
115 | await checkLogin(coreKitInstance);
116 | });
117 |
118 | await t.test("#able to sign", async function () {
119 | const msg = "hello world";
120 | const msgBuffer = Buffer.from(msg);
121 | const msgHash = keccak256(msgBuffer);
122 | const signature = sigToRSV(await coreKitInstance.sign(msgHash, true));
123 |
124 | const secp256k1 = new EC("secp256k1");
125 | const pubkey = secp256k1.recoverPubKey(msgHash, signature, signature.v) as EllipticPoint;
126 | const publicKeyPoint = bufferToElliptic(coreKitInstance.getPubKey());
127 | assert(pubkey.eq(publicKeyPoint));
128 | });
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/tests/ed25519.spec.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 | import test from "node:test";
3 |
4 | import { EllipticPoint } from "@tkey/common-types";
5 | import { UX_MODE_TYPE } from "@toruslabs/customauth";
6 | import { tssLib } from "@toruslabs/tss-frost-lib";
7 | import BN from "bn.js";
8 |
9 | import { AsyncStorage, COREKIT_STATUS, ed25519, MemoryStorage, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
10 | import { bufferToElliptic, criticalResetAccount, mockLogin, mockLogin2 } from "./setup";
11 |
12 | type TestVariable = {
13 | web3AuthNetwork: WEB3AUTH_NETWORK_TYPE;
14 | uxMode: UX_MODE_TYPE | "nodejs";
15 | manualSync?: boolean;
16 | email: string;
17 | };
18 |
19 | const defaultTestEmail = "testEmailForLoginEd25519";
20 | const variable: TestVariable[] = [
21 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", email: defaultTestEmail },
22 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, email: defaultTestEmail },
23 |
24 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", manualSync: true, email: defaultTestEmail },
25 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, manualSync: true, email: defaultTestEmail },
26 | ];
27 |
28 | const checkLogin = async (coreKitInstance: Web3AuthMPCCoreKit, accountIndex = 0) => {
29 | const keyDetails = coreKitInstance.getKeyDetails();
30 | assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.LOGGED_IN);
31 | assert.strictEqual(keyDetails.requiredFactors, 0);
32 | const factorkey = coreKitInstance.getCurrentFactorKey();
33 | await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex"), {
34 | accountIndex,
35 | });
36 | };
37 |
38 | const storageInstance = new MemoryStorage();
39 |
40 | variable.forEach((testVariable) => {
41 | const { web3AuthNetwork, uxMode, manualSync, email } = testVariable;
42 | const newCoreKitInstance = () =>
43 | new Web3AuthMPCCoreKit({
44 | web3AuthClientId: "torus-key-test",
45 | web3AuthNetwork,
46 | baseUrl: "http://localhost:3000",
47 | uxMode,
48 | tssLib,
49 | storage: storageInstance,
50 | manualSync,
51 | });
52 |
53 | async function resetAccount() {
54 | const resetInstance = newCoreKitInstance();
55 | const { idToken, parsedToken } = await mockLogin(email);
56 | await resetInstance.init({ handleRedirectResult: false, rehydrate: false });
57 | await resetInstance.loginWithJWT({
58 | verifier: "torus-test-health",
59 | verifierId: parsedToken.email,
60 | idToken,
61 | });
62 | await criticalResetAccount(resetInstance);
63 | await new AsyncStorage(resetInstance._storageKey, storageInstance).resetStore();
64 | }
65 |
66 | const testNameSuffix = JSON.stringify(testVariable);
67 |
68 | let checkPubKey: EllipticPoint;
69 | let checkTssShare: BN;
70 |
71 | test(`#Login Test with JWT + logout: ${testNameSuffix}`, async (t) => {
72 | await resetAccount();
73 | await t.test("#Login", async function () {
74 | const coreKitInstance = newCoreKitInstance();
75 |
76 | // mocklogin
77 | const { idToken, parsedToken } = await mockLogin(email);
78 | await coreKitInstance.init({ handleRedirectResult: false });
79 | await coreKitInstance.loginWithJWT({
80 | verifier: "torus-test-health",
81 | verifierId: parsedToken.email,
82 | idToken,
83 | });
84 | // get key details
85 | await checkLogin(coreKitInstance);
86 |
87 | checkPubKey = bufferToElliptic(coreKitInstance.getPubKey(), coreKitInstance.tKey.tssCurve);
88 | const factorkey = coreKitInstance.getCurrentFactorKey();
89 | const { tssShare } = await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex"), {
90 | threshold: 0,
91 | });
92 | checkTssShare = tssShare;
93 |
94 | if (manualSync) {
95 | await coreKitInstance.commitChanges();
96 | }
97 | // check whether the public key and tss share is same as old sdks
98 | });
99 |
100 | await t.test("#relogin ", async function () {
101 | const coreKitInstance = newCoreKitInstance();
102 | // rehydrate
103 | await coreKitInstance.init({ handleRedirectResult: false });
104 | await checkLogin(coreKitInstance);
105 |
106 | // logout
107 | await coreKitInstance.logout();
108 |
109 | // rehydrate should fail
110 | await coreKitInstance.init({
111 | rehydrate: false,
112 | handleRedirectResult: false,
113 | });
114 | assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.INITIALIZED);
115 | assert.throws(() => coreKitInstance.getCurrentFactorKey());
116 |
117 | // relogin
118 | const { idToken, parsedToken } = await mockLogin(email);
119 | await coreKitInstance.loginWithJWT({
120 | verifier: "torus-test-health",
121 | verifierId: parsedToken.email,
122 | idToken,
123 | });
124 |
125 | // get key details
126 | await checkLogin(coreKitInstance);
127 | const newPubKey = bufferToElliptic(coreKitInstance.getPubKey(), coreKitInstance.tKey.tssCurve);
128 | const factorkey = coreKitInstance.getCurrentFactorKey();
129 | const { tssShare: newTssShare } = await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex"));
130 | assert(checkPubKey.eq(newPubKey));
131 | assert(checkTssShare.eq(newTssShare));
132 | });
133 |
134 | await t.test("#able to sign", async function () {
135 | const coreKitInstance = newCoreKitInstance();
136 | await coreKitInstance.init({ handleRedirectResult: false, rehydrate: false });
137 | const localToken = await mockLogin2(email);
138 | await coreKitInstance.loginWithJWT({
139 | verifier: "torus-test-health",
140 | verifierId: email,
141 | idToken: localToken.idToken,
142 | });
143 | const msg = "hello world";
144 | const msgBuffer = Buffer.from(msg);
145 |
146 | const signature = ed25519().makeSignature((await coreKitInstance.sign(msgBuffer)).toString("hex"));
147 | const valid = ed25519().verify(msgBuffer, signature, coreKitInstance.getPubKeyEd25519());
148 | assert(valid);
149 | });
150 | });
151 | });
152 |
--------------------------------------------------------------------------------
/tests/factors.spec.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 | import test from "node:test";
3 |
4 | import { EllipticPoint, Point } from "@tkey/common-types";
5 | import { factorKeyCurve } from "@tkey/tss";
6 | import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib";
7 | import { tssLib as tssLibFROST } from "@toruslabs/tss-frost-lib";
8 | import BN from "bn.js";
9 |
10 | import { COREKIT_STATUS, IAsyncStorage, IStorage, MemoryStorage, TssLibType, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src";
11 | import { AsyncMemoryStorage, bufferToElliptic, criticalResetAccount, mockLogin } from "./setup";
12 |
13 | type FactorTestVariable = {
14 | manualSync?: boolean;
15 | storage?: IAsyncStorage | IStorage;
16 | email: string;
17 | tssLib?: TssLibType;
18 | };
19 |
20 | function getPubKeys(kit: Web3AuthMPCCoreKit, indices: number[]): EllipticPoint[] {
21 | if (!kit.supportsAccountIndex) {
22 | indices = indices.filter((i) => i === 0);
23 | }
24 | const pubKeys = indices.map((i) => {
25 | kit.setTssWalletIndex(i);
26 | return bufferToElliptic(kit.getPubKey());
27 | });
28 | return pubKeys;
29 | }
30 |
31 | export const FactorManipulationTest = async (testVariable: FactorTestVariable) => {
32 | const { email, tssLib } = testVariable;
33 | const newInstance = async () => {
34 | const instance = new Web3AuthMPCCoreKit({
35 | web3AuthClientId: "torus-key-test",
36 | web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET,
37 | baseUrl: "http://localhost:3000",
38 | uxMode: "nodejs",
39 | tssLib: tssLib || tssLibDKLS,
40 | storage: testVariable.storage,
41 | manualSync: testVariable.manualSync,
42 | });
43 |
44 | const { idToken, parsedToken } = await mockLogin(email);
45 | await instance.init({ handleRedirectResult: false, rehydrate: false });
46 | await instance.loginWithJWT({
47 | verifier: "torus-test-health",
48 | verifierId: parsedToken.email,
49 | idToken,
50 | });
51 | return instance;
52 | };
53 |
54 | async function beforeTest() {
55 | const resetInstance = await newInstance();
56 | await criticalResetAccount(resetInstance);
57 | await resetInstance.logout();
58 | }
59 |
60 | await test(`#Factor manipulation - manualSync ${testVariable.manualSync} `, async function (t) {
61 | await beforeTest();
62 |
63 | await t.test("should be able to create factor", async function () {
64 | const coreKitInstance = await newInstance();
65 | assert.equal(coreKitInstance.status, COREKIT_STATUS.LOGGED_IN);
66 |
67 | if (coreKitInstance.supportsAccountIndex) {
68 | coreKitInstance.setTssWalletIndex(1);
69 | }
70 | const tssPubKeys = getPubKeys(coreKitInstance, [0, 1, 99]);
71 |
72 | const firstFactor = coreKitInstance.getCurrentFactorKey();
73 | // try delete hash factor factor
74 | await assert.rejects(async () => {
75 | const pt = Point.fromScalar(firstFactor.factorKey, factorKeyCurve);
76 | await coreKitInstance.deleteFactor(pt);
77 | });
78 |
79 | // create factor
80 | const factorKey1 = await coreKitInstance.createFactor({
81 | shareType: TssShareType.DEVICE,
82 | });
83 |
84 | if (coreKitInstance.supportsAccountIndex) {
85 | coreKitInstance.setTssWalletIndex(2);
86 | }
87 | const factorKey2 = await coreKitInstance.createFactor({
88 | shareType: TssShareType.RECOVERY,
89 | });
90 |
91 | // sync
92 | if (testVariable.manualSync) {
93 | await coreKitInstance.commitChanges();
94 | }
95 |
96 | const tssPubKeysPost = getPubKeys(coreKitInstance, [0, 1, 99]);
97 |
98 | // clear session prevent rehydration
99 | await coreKitInstance.logout();
100 |
101 | // new instance
102 | const instance2 = await newInstance();
103 | assert.strictEqual(instance2.getTssFactorPub().length, 3);
104 |
105 | // try inputFactor ( set as active factor )
106 |
107 | // delete factor
108 | if (coreKitInstance.supportsAccountIndex) {
109 | instance2.setTssWalletIndex(0);
110 | }
111 | const pt = Point.fromScalar(new BN(factorKey1, "hex"), factorKeyCurve);
112 | await instance2.deleteFactor(pt);
113 |
114 | // delete factor
115 | if (coreKitInstance.supportsAccountIndex) {
116 | instance2.setTssWalletIndex(1);
117 | }
118 | const pt2 = Point.fromScalar(new BN(factorKey2, "hex"), factorKeyCurve);
119 | await instance2.deleteFactor(pt2);
120 |
121 | if (testVariable.manualSync) {
122 | await instance2.commitChanges();
123 | }
124 |
125 | const tssPubKeysPost2 = getPubKeys(instance2, [0, 1, 99]);
126 |
127 | tssPubKeys.forEach((pk, i) => {
128 | assert(pk.eq(tssPubKeysPost[i]));
129 | assert(pk.eq(tssPubKeysPost2[i]));
130 | });
131 |
132 | // new instance
133 | const instance3 = await newInstance();
134 | assert.strictEqual(instance3.getTssFactorPub().length, 1);
135 | });
136 |
137 | // enable mfa
138 |
139 | await t.test("enable MFA", async function () {
140 | const instance = await newInstance();
141 | assert.strictEqual(instance.status, COREKIT_STATUS.LOGGED_IN);
142 |
143 | if (instance.supportsAccountIndex) {
144 | instance.setTssWalletIndex(1);
145 | }
146 | const recoverFactor = await instance.enableMFA({});
147 |
148 | if (testVariable.manualSync) {
149 | await instance.commitChanges();
150 | }
151 |
152 | // to prevent rehydration ( rehydrate session id store in BrowserStorage)
153 | await instance.logout();
154 |
155 | // new instance
156 | const instance2 = await newInstance();
157 | assert.strictEqual(instance2.status, COREKIT_STATUS.REQUIRED_SHARE);
158 |
159 | const browserFactor = await instance2.getDeviceFactor();
160 |
161 | const factorBN = new BN(recoverFactor, "hex")
162 |
163 | // login with mfa factor
164 | await instance2.inputFactorKey(new BN(recoverFactor, "hex"));
165 | assert.strictEqual(instance2.status, COREKIT_STATUS.LOGGED_IN);
166 | await instance2.logout();
167 |
168 | // new instance
169 | const instance3 = await newInstance();
170 | assert.strictEqual(instance3.status, COREKIT_STATUS.REQUIRED_SHARE);
171 |
172 |
173 |
174 | try {
175 | await instance3.inputFactorKey(factorBN.subn(1));
176 | throw Error("should not be able to input factor");
177 | } catch (e) {
178 | assert(e instanceof Error);
179 | }
180 |
181 | await instance3.inputFactorKey(new BN(browserFactor, "hex"));
182 | assert.strictEqual(instance3.status, COREKIT_STATUS.LOGGED_IN);
183 | });
184 | });
185 | };
186 |
187 | const variable: FactorTestVariable[] = [
188 | { manualSync: true, storage: new MemoryStorage(), email: "testmail1012" },
189 | { manualSync: false, storage: new MemoryStorage(), email: "testmail1013" },
190 |
191 | { manualSync: true, storage: new AsyncMemoryStorage(), email: "testmail1014" },
192 | { manualSync: false, storage: new AsyncMemoryStorage(), email: "testmail1015" },
193 |
194 | { manualSync: true, storage: new MemoryStorage(), email: "testmail1012ed25519", tssLib: tssLibFROST },
195 | ];
196 |
197 | variable.forEach(async (testVariable) => {
198 | await FactorManipulationTest(testVariable);
199 | });
200 |
--------------------------------------------------------------------------------
/tests/gating.spec.ts:
--------------------------------------------------------------------------------
1 | import test from "node:test";
2 |
3 | import { UX_MODE_TYPE } from "@toruslabs/customauth";
4 | import { tssLib } from "@toruslabs/tss-dkls-lib";
5 | import assert from "assert";
6 |
7 | import { COREKIT_STATUS, MemoryStorage, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
8 | import { criticalResetAccount, mockLogin } from "./setup";
9 |
10 | type TestVariable = {
11 | description: string;
12 | web3AuthNetwork: WEB3AUTH_NETWORK_TYPE;
13 | web3ClientID: string;
14 | uxMode: UX_MODE_TYPE | "nodejs";
15 | manualSync?: boolean;
16 | email: string;
17 | expectedErrorThrown: boolean;
18 | };
19 |
20 | const defaultTestEmail = "testEmail1";
21 | const variable: TestVariable[] = [
22 | {
23 | description: "should not be gated when on devnet",
24 | web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET,
25 | uxMode: "nodejs",
26 | email: defaultTestEmail,
27 | web3ClientID: "torus-key-test",
28 | expectedErrorThrown: false,
29 | },
30 | {
31 | description: "should be gated and pass when on mainnet with client id on enterprise plan",
32 | web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET,
33 | uxMode: "nodejs",
34 | email: defaultTestEmail,
35 | web3ClientID: "BJ57yveG_XBLqZUpjtJCnJMrord0AaXpd_9OSy4HzkxpnpPn6Co73h-vR6GEI1VogtW4yMHq13GNPKmVpliFXY0",
36 | expectedErrorThrown: false,
37 | },
38 | {
39 | description: "should be gated and throw an error when on mainnet with client id on growth plan",
40 | web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET,
41 | uxMode: "nodejs",
42 | email: defaultTestEmail,
43 | web3ClientID: "BCriFlI9ihm81N-bc7x6N-xbqwBLuxfRDMmSH87spKH27QTNOPj1W9s2K3-mp9NzXuaRiqxvAGHyuGlXG5wLD1g",
44 | expectedErrorThrown: true,
45 | },
46 | ];
47 |
48 | variable.forEach((testVariable) => {
49 | const { web3AuthNetwork, uxMode, manualSync, email, web3ClientID: web3AuthClientId, expectedErrorThrown } = testVariable;
50 | const coreKitInstance = new Web3AuthMPCCoreKit({
51 | web3AuthClientId,
52 | web3AuthNetwork,
53 | baseUrl: "http://localhost:3000",
54 | uxMode,
55 | tssLib,
56 | storage: new MemoryStorage(),
57 | manualSync,
58 | });
59 |
60 | const testNameSuffix = testVariable.description;
61 | test(`#Gating test : ${testNameSuffix}`, async (t) => {
62 | async function beforeTest() {
63 | if (coreKitInstance.status === COREKIT_STATUS.INITIALIZED) await criticalResetAccount(coreKitInstance);
64 | }
65 |
66 | t.after(async function () {
67 | // after all test tear down
68 | });
69 |
70 | await beforeTest();
71 | await t.test("#Login ", async function () {
72 | // mocklogin
73 | const { idToken, parsedToken } = await mockLogin(email);
74 |
75 | if (expectedErrorThrown) {
76 | await assert.rejects(() => coreKitInstance.init({ handleRedirectResult: false, rehydrate: false }));
77 | return;
78 | }
79 |
80 | await coreKitInstance.init({ handleRedirectResult: false, rehydrate: false });
81 |
82 | await coreKitInstance.loginWithJWT({
83 | verifier: "torus-test-health",
84 | verifierId: parsedToken.email,
85 | idToken,
86 | });
87 | });
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/tests/importRecovery.spec.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 | import test from "node:test";
3 |
4 | import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib";
5 | import { tssLib as tssLibFROST } from "@toruslabs/tss-frost-lib";
6 |
7 | import { AsyncStorage, MemoryStorage, TssLibType, TssShareType, WEB3AUTH_NETWORK } from "../src";
8 | import { bufferToElliptic, criticalResetAccount, newCoreKitLogInInstance } from "./setup";
9 |
10 | type ImportKeyTestVariable = {
11 | manualSync?: boolean;
12 | email: string;
13 | importKeyEmail: string;
14 | tssLib: TssLibType;
15 | };
16 |
17 | const storageInstance = new MemoryStorage();
18 | export const ImportTest = async (testVariable: ImportKeyTestVariable) => {
19 | async function newCoreKitInstance(email: string, importTssKey?: string) {
20 | return newCoreKitLogInInstance({
21 | network: WEB3AUTH_NETWORK.DEVNET,
22 | manualSync: testVariable.manualSync,
23 | email: email,
24 | storageInstance,
25 | tssLib: testVariable.tssLib,
26 | importTssKey,
27 | });
28 | }
29 |
30 | async function resetAccount(email: string) {
31 | const kit = await newCoreKitInstance(email);
32 | await criticalResetAccount(kit);
33 | await kit.logout();
34 | await new AsyncStorage(kit._storageKey, storageInstance).resetStore();
35 | }
36 |
37 | test(`import recover tss key : ${testVariable.manualSync}`, async function (t) {
38 | const beforeTest = async () => {
39 | await resetAccount(testVariable.email);
40 | await resetAccount(testVariable.importKeyEmail);
41 | };
42 |
43 | await beforeTest();
44 |
45 | await t.test("#recover Tss key using 2 factors key, import tss key to new oauth login", async function () {
46 | const coreKitInstance = await newCoreKitInstance(testVariable.email);
47 |
48 | // Create 2 factors which will be used to recover tss key.
49 | const factorKeyDevice = await coreKitInstance.createFactor({
50 | shareType: TssShareType.DEVICE,
51 | });
52 |
53 | const factorKeyRecovery = await coreKitInstance.createFactor({
54 | shareType: TssShareType.RECOVERY,
55 | });
56 |
57 | if (testVariable.manualSync) {
58 | await coreKitInstance.commitChanges();
59 | }
60 |
61 | // Export key and logout.
62 | const exportedTssKey1 = await coreKitInstance._UNSAFE_exportTssKey();
63 | await coreKitInstance.logout();
64 |
65 | // Recover key from any two factors.
66 | const recoveredTssKey = await coreKitInstance._UNSAFE_recoverTssKey([factorKeyDevice, factorKeyRecovery]);
67 | assert.strictEqual(recoveredTssKey, exportedTssKey1);
68 |
69 | // Initialize new instance and import existing key.
70 | const coreKitInstance2 = await newCoreKitInstance(testVariable.importKeyEmail, recoveredTssKey);
71 | if (testVariable.manualSync) {
72 | await coreKitInstance2.commitChanges();
73 | }
74 |
75 | // Export key.
76 | const exportedTssKey = await coreKitInstance2._UNSAFE_exportTssKey();
77 | assert.strictEqual(exportedTssKey, recoveredTssKey);
78 |
79 | // Check exported key corresponds to pub key.
80 | const coreKitInstance3 = await newCoreKitInstance(testVariable.importKeyEmail);
81 | const tssPubkey = bufferToElliptic(coreKitInstance3.getPubKey());
82 |
83 | const exportedTssKey3 = await coreKitInstance3._UNSAFE_exportTssKey();
84 | const tssCurve = coreKitInstance3.tKey.tssCurve;
85 | const exportedPub = tssCurve.keyFromPrivate(exportedTssKey3).getPublic();
86 | assert(tssPubkey.eq(exportedPub));
87 |
88 | // Check exported key corresponds to pub key for account index > 0.
89 | if (coreKitInstance3.supportsAccountIndex) {
90 | coreKitInstance3.setTssWalletIndex(1);
91 | const exportedTssKeyIndex1 = await coreKitInstance3._UNSAFE_exportTssKey();
92 | const exportedPubIndex1 = tssCurve.keyFromPrivate(exportedTssKeyIndex1).getPublic();
93 | const tssPubKeyIndex1 = bufferToElliptic(coreKitInstance3.getPubKey());
94 | assert(exportedPubIndex1.eq(tssPubKeyIndex1));
95 | }
96 | });
97 |
98 | t.afterEach(function () {
99 | return console.info("finished running recovery test");
100 | });
101 | t.after(function () {
102 | return console.info("finished running recovery tests");
103 | });
104 | });
105 | };
106 |
107 | const variable: ImportKeyTestVariable[] = [
108 | { manualSync: false, email: "emailexport", importKeyEmail: "emailimport", tssLib: tssLibDKLS },
109 | { manualSync: true, email: "emailexport", importKeyEmail: "emailimport", tssLib: tssLibDKLS },
110 | { manualSync: false, email: "emailexport_ed25519", importKeyEmail: "emailimport_ed25519", tssLib: tssLibFROST },
111 | ];
112 |
113 | variable.forEach(async (testVariable) => {
114 | await ImportTest(testVariable);
115 | });
116 |
--------------------------------------------------------------------------------
/tests/login.spec.ts:
--------------------------------------------------------------------------------
1 | import assert, { fail } from "node:assert";
2 | import test from "node:test";
3 |
4 | import { EllipticPoint } from "@tkey/common-types";
5 | import { UX_MODE_TYPE } from "@toruslabs/customauth";
6 | import { keccak256 } from "@toruslabs/metadata-helpers";
7 | import { tssLib } from "@toruslabs/tss-dkls-lib";
8 | import BN from "bn.js";
9 | import { ec as EC } from "elliptic";
10 |
11 | import { AsyncStorage, COREKIT_STATUS, MemoryStorage, sigToRSV, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
12 | import { bufferToElliptic, criticalResetAccount, mockLogin, mockLogin2, stringGen } from "./setup";
13 |
14 | type TestVariable = {
15 | web3AuthNetwork: WEB3AUTH_NETWORK_TYPE;
16 | uxMode: UX_MODE_TYPE | "nodejs";
17 | manualSync?: boolean;
18 |
19 | email: string;
20 | };
21 |
22 | const defaultTestEmail = "testEmailForLogin";
23 | const variable: TestVariable[] = [
24 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", email: defaultTestEmail },
25 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, email: defaultTestEmail },
26 |
27 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", manualSync: true, email: defaultTestEmail },
28 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, manualSync: true, email: defaultTestEmail },
29 | ];
30 |
31 | const checkLogin = async (coreKitInstance: Web3AuthMPCCoreKit, accountIndex = 0) => {
32 | const keyDetails = coreKitInstance.getKeyDetails();
33 | assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.LOGGED_IN);
34 | assert.strictEqual(keyDetails.requiredFactors, 0);
35 | const factorkey = coreKitInstance.getCurrentFactorKey();
36 | await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex"), {
37 | accountIndex,
38 | });
39 | };
40 |
41 | const storageInstance = new MemoryStorage();
42 |
43 | variable.forEach((testVariable) => {
44 | const { web3AuthNetwork, uxMode, manualSync, email } = testVariable;
45 | const newCoreKitInstance = () =>
46 | new Web3AuthMPCCoreKit({
47 | web3AuthClientId: "torus-key-test",
48 | web3AuthNetwork,
49 | baseUrl: "http://localhost:3000",
50 | uxMode,
51 | tssLib,
52 | storage: storageInstance,
53 | manualSync,
54 | });
55 |
56 | const testNameSuffix = JSON.stringify(testVariable);
57 |
58 | let checkPubKey: EllipticPoint;
59 | let checkTssShare: BN;
60 |
61 | test(`#Login Test with JWT + logout : ${testNameSuffix}`, async (t) => {
62 | async function beforeTest() {
63 | const resetInstance = new Web3AuthMPCCoreKit({
64 | web3AuthClientId: "torus-key-test",
65 | web3AuthNetwork,
66 | baseUrl: "http://localhost:3000",
67 | uxMode,
68 | tssLib,
69 | storage: storageInstance,
70 | manualSync,
71 | });
72 | const { idToken, parsedToken } = await mockLogin(email);
73 | await resetInstance.init({ handleRedirectResult: false, rehydrate: false });
74 | await resetInstance.loginWithJWT({
75 | verifier: "torus-test-health",
76 | verifierId: parsedToken.email,
77 | idToken,
78 | });
79 | await criticalResetAccount(resetInstance);
80 | await new AsyncStorage(resetInstance._storageKey, storageInstance).resetStore();
81 | }
82 |
83 | await beforeTest();
84 | await t.test("#Login", async function () {
85 | const coreKitInstance = newCoreKitInstance();
86 |
87 | // mocklogin
88 | const { idToken, parsedToken } = await mockLogin(email);
89 | await coreKitInstance.init({ handleRedirectResult: false });
90 | await coreKitInstance.loginWithJWT({
91 | verifier: "torus-test-health",
92 | verifierId: parsedToken.email,
93 | idToken,
94 | });
95 | // get key details
96 | await checkLogin(coreKitInstance);
97 |
98 | checkPubKey = bufferToElliptic(coreKitInstance.getPubKey());
99 | const factorkey = coreKitInstance.getCurrentFactorKey();
100 | const { tssShare } = await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex"), {
101 | threshold: 0,
102 | });
103 | checkTssShare = tssShare;
104 |
105 | if (manualSync) {
106 | await coreKitInstance.commitChanges();
107 | }
108 | // check whether the public key and tss share is same as old sdks
109 | });
110 |
111 | await t.test("#relogin ", async function () {
112 | const coreKitInstance = newCoreKitInstance();
113 | // rehydrate
114 | await coreKitInstance.init({ handleRedirectResult: false });
115 | await checkLogin(coreKitInstance);
116 |
117 | // logout
118 | await coreKitInstance.logout();
119 |
120 | // rehydrate should fail
121 | await coreKitInstance.init({
122 | rehydrate: false,
123 | handleRedirectResult: false,
124 | });
125 | assert.strictEqual(coreKitInstance.status, COREKIT_STATUS.INITIALIZED);
126 | assert.throws(() => coreKitInstance.getCurrentFactorKey());
127 |
128 | // relogin
129 | const { idToken, parsedToken } = await mockLogin(email);
130 | await coreKitInstance.loginWithJWT({
131 | verifier: "torus-test-health",
132 | verifierId: parsedToken.email,
133 | idToken,
134 | });
135 |
136 | // get key details
137 | await checkLogin(coreKitInstance);
138 | const newPubKey = bufferToElliptic(coreKitInstance.getPubKey());
139 | const factorkey = coreKitInstance.getCurrentFactorKey();
140 | const { tssShare: newTssShare } = await coreKitInstance.tKey.getTSSShare(new BN(factorkey.factorKey, "hex"));
141 | assert(checkPubKey.eq(newPubKey));
142 | assert(checkTssShare.eq(newTssShare));
143 | });
144 |
145 | await t.test("#able to sign", async function () {
146 | const coreKitInstance = newCoreKitInstance();
147 | await coreKitInstance.init({ handleRedirectResult: false, rehydrate: false });
148 | const localToken = await mockLogin2(email);
149 | await coreKitInstance.loginWithJWT({
150 | verifier: "torus-test-health",
151 | verifierId: email,
152 | idToken: localToken.idToken,
153 | });
154 | const msg = "hello world";
155 | const msgBuffer = Buffer.from(msg);
156 | const msgHash = keccak256(msgBuffer);
157 | const secp256k1 = new EC("secp256k1");
158 |
159 | // Sign hash.
160 | const signature = sigToRSV(await coreKitInstance.sign(msgHash, true));
161 | const pubkey = secp256k1.recoverPubKey(msgHash, signature, signature.v) as EllipticPoint;
162 | const publicKeyPoint = bufferToElliptic(coreKitInstance.getPubKey());
163 | assert(pubkey.eq(publicKeyPoint));
164 |
165 | // Sign full message.
166 | const signature2 = sigToRSV(await coreKitInstance.sign(msgBuffer));
167 | const pubkey2 = secp256k1.recoverPubKey(msgHash, signature2, signature2.v) as EllipticPoint;
168 | assert(pubkey2.eq(publicKeyPoint));
169 |
170 | // should fail to sign due to preSignValidation
171 | {
172 | coreKitInstance.setPreSigningHook(async ({ data }) => {
173 | return {
174 | success: false,
175 | data: Buffer.from(data).toString("hex")
176 | }
177 | });
178 |
179 | try {
180 | await coreKitInstance.sign(msgHash, true);
181 | fail("Should fail signing")
182 | } catch (err) {
183 | assert(err);
184 | }
185 | };
186 |
187 | // should succeed to sign
188 | {
189 | coreKitInstance.setPreSigningHook(async ({ data }) => {
190 | return {
191 | success: true,
192 | data: Buffer.from(data).toString("hex")
193 | }
194 | });
195 | const signature = sigToRSV(await coreKitInstance.sign(msgHash, true));
196 | const pubkey = secp256k1.recoverPubKey(msgHash, signature, signature.v) as EllipticPoint;
197 | const publicKeyPoint = bufferToElliptic(coreKitInstance.getPubKey());
198 | assert(pubkey.eq(publicKeyPoint));
199 | };
200 | });
201 |
202 |
203 | await t.test("#Login and sign with different account/wallet index", async function () {
204 | const vid = stringGen(10);
205 | const coreKitInstance = newCoreKitInstance();
206 | // mock login with random
207 | const { idToken: idToken2, parsedToken: parsedToken2 } = await mockLogin(vid);
208 | await coreKitInstance.init({ handleRedirectResult: false, rehydrate: false });
209 | await coreKitInstance.loginWithJWT({
210 | verifier: "torus-test-health",
211 | verifierId: parsedToken2.email,
212 | idToken: idToken2,
213 | });
214 |
215 | const secp256k1 = new EC("secp256k1");
216 | await coreKitInstance.setTssWalletIndex(0);
217 |
218 | const msg = "hello world 1";
219 | const msgBuffer = Buffer.from(msg);
220 | const msgHash = keccak256(msgBuffer);
221 | const signature1 = sigToRSV(await coreKitInstance.sign(msgHash, true));
222 |
223 | const pubkeyIndex0 = secp256k1.recoverPubKey(msgHash, signature1, signature1.v);
224 | const publicKeyPoint0 = bufferToElliptic(coreKitInstance.getPubKey());
225 | assert(pubkeyIndex0.eq(publicKeyPoint0));
226 |
227 | await coreKitInstance.setTssWalletIndex(1);
228 |
229 | const msg1 = "hello world 2";
230 | const msgBuffer1 = Buffer.from(msg1);
231 | const msgHash1 = keccak256(msgBuffer1);
232 |
233 | const signature2 = sigToRSV(await coreKitInstance.sign(msgHash1, true));
234 |
235 | const pubkeyIndex1 = secp256k1.recoverPubKey(msgHash1, signature2, signature2.v) as EllipticPoint;
236 | const publicKeyPoint1 = bufferToElliptic(coreKitInstance.getPubKey());
237 | assert(pubkeyIndex1.eq(publicKeyPoint1));
238 |
239 | await checkLogin(coreKitInstance, 1);
240 |
241 | await coreKitInstance.setTssWalletIndex(2);
242 |
243 | const msg2 = "hello world 3";
244 | const msgBuffer2 = Buffer.from(msg2);
245 | const msgHash2 = keccak256(msgBuffer2);
246 | const signature3 = sigToRSV(await coreKitInstance.sign(msgHash2, true));
247 |
248 | const pubkeyIndex2 = secp256k1.recoverPubKey(msgHash2, signature3, signature3.v) as EllipticPoint;
249 | const publicKeyPoint2 = bufferToElliptic(coreKitInstance.getPubKey());
250 | assert(pubkeyIndex2.eq(publicKeyPoint2));
251 |
252 | await checkLogin(coreKitInstance, 2);
253 |
254 | assert(!publicKeyPoint0.eq(publicKeyPoint1));
255 | assert(!publicKeyPoint0.eq(publicKeyPoint2));
256 | assert(!publicKeyPoint1.eq(publicKeyPoint2));
257 |
258 | if (manualSync) {
259 | await coreKitInstance.commitChanges();
260 | }
261 | const coreKitInstance3 = newCoreKitInstance();
262 | // mock login with random
263 | const { idToken: idToken3, parsedToken: parsedToken3 } = await mockLogin(vid);
264 | await coreKitInstance3.init({ handleRedirectResult: false, rehydrate: false });
265 | await coreKitInstance3.loginWithJWT({
266 | verifier: "torus-test-health",
267 | verifierId: parsedToken3.email,
268 | idToken: idToken3,
269 | });
270 |
271 | coreKitInstance.setTssWalletIndex(0);
272 | const pubkey3index0 = bufferToElliptic(coreKitInstance3.getPubKey());
273 | coreKitInstance3.setTssWalletIndex(1);
274 | const pubkey3index1 = bufferToElliptic(coreKitInstance3.getPubKey());
275 | coreKitInstance3.setTssWalletIndex(2);
276 | const pubkey3index2 = bufferToElliptic(coreKitInstance3.getPubKey());
277 |
278 | assert(pubkeyIndex0.eq(pubkey3index0));
279 | assert(pubkeyIndex1.eq(pubkey3index1));
280 | assert(pubkeyIndex2.eq(pubkey3index2));
281 | });
282 | });
283 | });
284 |
--------------------------------------------------------------------------------
/tests/securityQuestion.spec.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 | import test from "node:test";
3 |
4 | // import { getPubKeyPoint } from "@tkey-mpc/common-types";
5 | import { UX_MODE_TYPE } from "@toruslabs/customauth";
6 | import { tssLib } from "@toruslabs/tss-dkls-lib";
7 | import BN from "bn.js";
8 |
9 | import {
10 | AsyncStorage,
11 | MemoryStorage,
12 | SupportedStorageType,
13 | TssSecurityQuestion,
14 | WEB3AUTH_NETWORK,
15 | WEB3AUTH_NETWORK_TYPE,
16 | Web3AuthMPCCoreKit,
17 | } from "../src";
18 | import { criticalResetAccount, mockLogin } from "./setup";
19 |
20 | type TestVariable = {
21 | web3AuthNetwork: WEB3AUTH_NETWORK_TYPE;
22 | uxMode: UX_MODE_TYPE | "nodejs";
23 | manualSync?: boolean;
24 | storage?: SupportedStorageType;
25 | };
26 |
27 | const storageInstance = new MemoryStorage();
28 | export const TssSecurityQuestionsTest = async (newInstance: () => Promise, testVariable: TestVariable) => {
29 | test(`#Tss Security Question - ${testVariable.manualSync} `, async function (t) {
30 | async function beforeTest() {
31 | const coreKitInstance = await newInstance();
32 | await criticalResetAccount(coreKitInstance);
33 | await coreKitInstance.logout();
34 |
35 | await new AsyncStorage(coreKitInstance._storageKey, storageInstance).resetStore();
36 | }
37 | t.afterEach(function () {
38 | return console.log("finished running test");
39 | });
40 | t.after(function () {
41 | return console.log("finished running tests");
42 | });
43 |
44 | await beforeTest();
45 | await t.test("should work", async function () {
46 | // set security question
47 | const instance = await newInstance();
48 | instance.setTssWalletIndex(1);
49 | const question = "test question";
50 | const answer = "test answer";
51 | const newQuestion = "new question";
52 | const newAnswer = "new answer";
53 | // const shareType = TssShareType.DEVICE;
54 |
55 | const securityQuestion = new TssSecurityQuestion();
56 | await securityQuestion.setSecurityQuestion({
57 | mpcCoreKit: instance,
58 | question,
59 | answer,
60 | // shareType,
61 | });
62 |
63 | // recover factor
64 | const factor = await securityQuestion.recoverFactor(instance, answer);
65 | // check factor
66 | await instance.tKey.getTSSShare(new BN(factor, "hex"));
67 | // check wrong answer
68 | await assert.rejects(() => securityQuestion.recoverFactor(instance, "wrong answer"));
69 |
70 | // change factor
71 | await securityQuestion.changeSecurityQuestion({
72 | mpcCoreKit: instance,
73 | newQuestion,
74 | newAnswer,
75 | answer,
76 | });
77 | // recover factor
78 | // check factor
79 | const newFactor = await securityQuestion.recoverFactor(instance, newAnswer);
80 | await instance.tKey.getTSSShare(new BN(newFactor, "hex"));
81 |
82 | instance.setTssWalletIndex(0);
83 |
84 | // recover factor
85 | // check factor
86 | const newFactor2 = await securityQuestion.recoverFactor(instance, newAnswer);
87 | await instance.tKey.getTSSShare(new BN(newFactor, "hex"));
88 |
89 | instance.setTssWalletIndex(2);
90 |
91 | // recover factor
92 | // check factor
93 | const newFactor3 = await securityQuestion.recoverFactor(instance, newAnswer);
94 | await instance.tKey.getTSSShare(new BN(newFactor, "hex"));
95 |
96 | assert.strictEqual(newFactor, newFactor2);
97 | assert.strictEqual(newFactor, newFactor3);
98 |
99 | await assert.rejects(() => instance.tKey.getTSSShare(new BN(factor, "hex")));
100 |
101 | // recover factor
102 | // check wrong answer
103 | await assert.rejects(() => securityQuestion.recoverFactor(instance, answer));
104 |
105 | // delete factor
106 | await securityQuestion.deleteSecurityQuestion(instance);
107 |
108 | // recover factor
109 | await assert.rejects(() => securityQuestion.recoverFactor(instance, newAnswer));
110 | await assert.rejects(() => securityQuestion.recoverFactor(instance, answer));
111 |
112 | // input factor
113 | assert.strictEqual(true, true);
114 | });
115 | });
116 | };
117 |
118 | const variable: TestVariable[] = [
119 | // { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: UX_MODE.REDIRECT },
120 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT },
121 |
122 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", manualSync: false, storage: "memory" },
123 | // { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, manualSync: true },
124 | ];
125 |
126 | const email = "testmail99";
127 |
128 | variable.forEach(async (testVariable) => {
129 | const newCoreKitLogInInstance = async () => {
130 | const instance = new Web3AuthMPCCoreKit({
131 | web3AuthClientId: "torus-key-test",
132 | web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET,
133 | baseUrl: "http://localhost:3000",
134 | uxMode: "nodejs",
135 | tssLib,
136 | storage: storageInstance,
137 | manualSync: testVariable.manualSync,
138 | });
139 |
140 | const { idToken, parsedToken } = await mockLogin(email);
141 | await instance.init({ handleRedirectResult: false });
142 | await instance.loginWithJWT({
143 | verifier: "torus-test-health",
144 | verifierId: parsedToken.email,
145 | idToken,
146 | });
147 |
148 | return instance;
149 | };
150 |
151 | await TssSecurityQuestionsTest(newCoreKitLogInInstance, testVariable);
152 | });
153 |
--------------------------------------------------------------------------------
/tests/sessionTime.spec.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 | import test from "node:test";
3 |
4 | import { UX_MODE_TYPE } from "@toruslabs/customauth";
5 | import { tssLib } from "@toruslabs/tss-dkls-lib";
6 |
7 | import { COREKIT_STATUS, MemoryStorage, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
8 | import { criticalResetAccount, mockLogin } from "./setup";
9 |
10 | type TestVariable = {
11 | web3AuthNetwork: WEB3AUTH_NETWORK_TYPE;
12 | web3ClientID: string;
13 | uxMode: UX_MODE_TYPE | "nodejs";
14 | manualSync?: boolean;
15 | email: string;
16 | gated?: boolean;
17 | sessionTime?: number;
18 | disableSessionManager?: boolean;
19 | };
20 |
21 | const defaultTestEmail = "testEmail1";
22 |
23 | const isBasePlan = (id: string) => id === "BCriFlI9ihm81N-bc7x6N-xbqwBLuxfRDMmSH87spKH27QTNOPj1W9s2K3-mp9NzXuaRiqxvAGHyuGlXG5wLD1g";
24 | // BasePlan up to 1 day only
25 | const variable: TestVariable[] = [
26 | { web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", email: defaultTestEmail, web3ClientID: "torus-key-test", sessionTime: 3600 },
27 | {
28 | web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET,
29 | uxMode: "nodejs",
30 | email: defaultTestEmail,
31 | web3ClientID: "BJ57yveG_XBLqZUpjtJCnJMrord0AaXpd_9OSy4HzkxpnpPn6Co73h-vR6GEI1VogtW4yMHq13GNPKmVpliFXY0",
32 | sessionTime: 7200,
33 | },
34 | {
35 | web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET,
36 | uxMode: "nodejs",
37 | email: defaultTestEmail,
38 | web3ClientID: "BCriFlI9ihm81N-bc7x6N-xbqwBLuxfRDMmSH87spKH27QTNOPj1W9s2K3-mp9NzXuaRiqxvAGHyuGlXG5wLD1g",
39 | sessionTime: 172800,
40 | },
41 | {
42 | web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET,
43 | uxMode: "nodejs",
44 | email: defaultTestEmail,
45 | web3ClientID: "BJ57yveG_XBLqZUpjtJCnJMrord0AaXpd_9OSy4HzkxpnpPn6Co73h-vR6GEI1VogtW4yMHq13GNPKmVpliFXY0",
46 | sessionTime: 7200,
47 | disableSessionManager : false
48 | },
49 |
50 | {
51 | web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET,
52 | uxMode: "nodejs",
53 | email: defaultTestEmail,
54 | web3ClientID: "BJ57yveG_XBLqZUpjtJCnJMrord0AaXpd_9OSy4HzkxpnpPn6Co73h-vR6GEI1VogtW4yMHq13GNPKmVpliFXY0",
55 | sessionTime: 7200,
56 | disableSessionManager : true
57 | },
58 | ];
59 |
60 | const storageInstance = new MemoryStorage();
61 | variable.forEach(async (testVariable) => {
62 | const { web3AuthNetwork, uxMode, manualSync, email, web3ClientID: web3AuthClientId, sessionTime, disableSessionManager } = testVariable;
63 |
64 |
65 | await test(`#Variable SessionTime test : ${JSON.stringify({ sessionTime: testVariable.sessionTime })} - disableSessionManager: ${disableSessionManager} client_id: ${web3AuthClientId}`, async (t) => {
66 | const coreKitInstance = new Web3AuthMPCCoreKit({
67 | web3AuthClientId,
68 | web3AuthNetwork,
69 | baseUrl: "http://localhost:3000",
70 | uxMode,
71 | tssLib,
72 | storage: storageInstance,
73 | manualSync,
74 | sessionTime,
75 | disableSessionManager,
76 | });
77 |
78 | async function beforeTest() {
79 | if (coreKitInstance.status === COREKIT_STATUS.INITIALIZED) await criticalResetAccount(coreKitInstance);
80 | }
81 |
82 | t.after(async function () {
83 | // after all test tear down
84 | if (!isBasePlan(web3AuthClientId)) await coreKitInstance.logout();
85 | });
86 |
87 | await beforeTest();
88 |
89 | await t.test("`sessionTime` should be equal to `sessionTokenDuration` from #Login", async function (t) {
90 | // mocklogin
91 | const { idToken, parsedToken } = await mockLogin(email);
92 |
93 | await coreKitInstance.init({ handleRedirectResult: false }).catch((err) => {
94 | if ( !isBasePlan(testVariable.web3ClientID) ) {
95 | throw err;
96 | }
97 | });
98 |
99 | await coreKitInstance.loginWithJWT({
100 | verifier: "torus-test-health",
101 | verifierId: parsedToken.email,
102 | idToken,
103 | }).catch((err) => {
104 | if ( !isBasePlan(testVariable.web3ClientID) ) {
105 | throw err;
106 | }
107 | });
108 | if ( !isBasePlan(testVariable.web3ClientID) ) {
109 | // skip remaining test if is BasePlan
110 | return;
111 | }
112 |
113 | coreKitInstance.signatures.forEach((sig) => {
114 | const parsedSig = JSON.parse(sig);
115 | const parsedSigData = JSON.parse(atob(parsedSig.data));
116 |
117 | const sessionTokenDuration = parsedSigData.exp - Math.floor(Date.now() / 1000);
118 | // in success case, sessionTimeDiff (diff between provided sessionTime and generated session token duration from sss-service)
119 | // should not be more than 3s(supposed 3s as the network latency)
120 | const sessionTimeDiff = Math.abs(sessionTokenDuration - sessionTime);
121 | assert.strictEqual(sessionTimeDiff <= 3, true);
122 | });
123 |
124 | if (disableSessionManager) {
125 | assert.equal(coreKitInstance.sessionId , undefined);
126 | } else {
127 | assert.notEqual(coreKitInstance.sessionId , undefined);
128 | }
129 | });
130 |
131 |
132 | });
133 | });
134 |
--------------------------------------------------------------------------------
/tests/setup.ts:
--------------------------------------------------------------------------------
1 | import { EllipticPoint, secp256k1 } from "@tkey/common-types";
2 | import { tssLib } from "@toruslabs/tss-dkls-lib";
3 | import BN from "bn.js";
4 | import jwt, { Algorithm } from "jsonwebtoken";
5 | import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib";
6 |
7 | import { IAsyncStorage, IStorage, parseToken, TssLibType, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
8 |
9 | export const mockLogin2 = async (email: string) => {
10 | const req = new Request("https://li6lnimoyrwgn2iuqtgdwlrwvq0upwtr.lambda-url.eu-west-1.on.aws/", {
11 | method: "POST",
12 | headers: {
13 | "Content-Type": "application/json",
14 | },
15 | body: JSON.stringify({ verifier: "torus-key-test", scope: "email", extraPayload: { email }, alg: "ES256" }),
16 | });
17 |
18 | const resp = await fetch(req);
19 | const bodyJson = (await resp.json()) as { token: string };
20 | const idToken = bodyJson.token;
21 | const parsedToken = parseToken(idToken);
22 | return { idToken, parsedToken };
23 | };
24 |
25 | export const criticalResetAccount = async (coreKitInstance: Web3AuthMPCCoreKit): Promise => {
26 | // This is a critical function that should only be used for testing purposes
27 | // Resetting your account means clearing all the metadata associated with it from the metadata server
28 | // The key details will be deleted from our server and you will not be able to recover your account
29 | if (!coreKitInstance) {
30 | throw new Error("coreKitInstance is not set");
31 | }
32 |
33 | if (coreKitInstance.tKey.secp256k1Key) {
34 | await coreKitInstance.tKey.CRITICAL_deleteTkey();
35 | } else {
36 | await coreKitInstance.tKey.storageLayer.setMetadata({
37 | privKey: new BN(coreKitInstance.state.postBoxKey!, "hex"),
38 | input: { message: "KEY_NOT_FOUND" },
39 | });
40 | }
41 | };
42 |
43 | const privateKey = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCD7oLrcKae+jVZPGx52Cb/lKhdKxpXjl9eGNa1MlY57A==";
44 | const jwtPrivateKey = `-----BEGIN PRIVATE KEY-----\n${privateKey}\n-----END PRIVATE KEY-----`;
45 | const alg: Algorithm = "ES256";
46 |
47 | export function stringGen(len: number) {
48 | let text = "";
49 | const charset = "abcdefghijklmnopqrstuvwxyz0123456789";
50 |
51 | for (let i = 0; i < len; i++) {
52 | text += charset.charAt(Math.floor(Math.random() * charset.length));
53 | }
54 |
55 | return text;
56 | }
57 |
58 | export const mockLogin = async (email?: string) => {
59 | // if email is not passed generate a random email
60 | if (!email) {
61 | email = `${stringGen(10)}@${stringGen(5)}.${stringGen(3)}`;
62 | }
63 |
64 | const iat = Math.floor(Date.now() / 1000);
65 | const payload = {
66 | iss: "torus-key-test",
67 | aud: "torus-key-test",
68 | name: email,
69 | email,
70 | scope: "email",
71 | iat,
72 | eat: iat + 120,
73 | };
74 |
75 | const algo = {
76 | expiresIn: 120,
77 | algorithm: alg,
78 | };
79 |
80 | const token = jwt.sign(payload, jwtPrivateKey, algo);
81 | const idToken = token;
82 | const parsedToken = parseToken(idToken);
83 | return { idToken, parsedToken };
84 | };
85 |
86 | export type LoginFunc = (email: string) => Promise<{ idToken: string, parsedToken: any }>;
87 |
88 | export const newCoreKitLogInInstance = async ({
89 | network,
90 | manualSync,
91 | email,
92 | storageInstance,
93 | importTssKey,
94 | registerExistingSFAKey,
95 | login,
96 | }: {
97 | network: WEB3AUTH_NETWORK_TYPE;
98 | manualSync: boolean;
99 | email: string;
100 | storageInstance: IStorage | IAsyncStorage;
101 | tssLib?: TssLibType;
102 | importTssKey?: string;
103 | registerExistingSFAKey?: boolean;
104 | login?: LoginFunc;
105 | }) => {
106 | const instance = new Web3AuthMPCCoreKit({
107 | web3AuthClientId: "torus-key-test",
108 | web3AuthNetwork: network,
109 | baseUrl: "http://localhost:3000",
110 | uxMode: "nodejs",
111 | tssLib: tssLib || tssLibDKLS,
112 | storage: storageInstance,
113 | manualSync,
114 | });
115 |
116 | const { idToken, parsedToken } = login ? await login(email) : await mockLogin(email);
117 | await instance.init();
118 | await instance.loginWithJWT({
119 | verifier: "torus-test-health",
120 | verifierId: parsedToken.email,
121 | idToken,
122 | importTssKey,
123 | registerExistingSFAKey
124 | });
125 |
126 | return instance;
127 | };
128 |
129 | export const loginWithSFA = async ({
130 | network,
131 | manualSync,
132 | email,
133 | storageInstance,
134 | login,
135 | }: {
136 | network: WEB3AUTH_NETWORK_TYPE;
137 | manualSync: boolean;
138 | email: string;
139 | storageInstance: IStorage | IAsyncStorage;
140 | tssLib?: TssLibType;
141 | login?: LoginFunc;
142 | }) => {
143 | const instance = new Web3AuthMPCCoreKit({
144 | web3AuthClientId: "torus-key-test",
145 | web3AuthNetwork: network,
146 | baseUrl: "http://localhost:3000",
147 | uxMode: "nodejs",
148 | tssLib: tssLib || tssLibDKLS,
149 | storage: storageInstance,
150 | manualSync,
151 | });
152 |
153 | const { idToken, parsedToken } = login ? await login(email) : await mockLogin(email);
154 | await instance.init();
155 | const nodeDetails = await instance.torusSp.customAuthInstance.nodeDetailManager.getNodeDetails({
156 | verifier: "torus-test-health",
157 | verifierId: parsedToken.email,
158 | })
159 | return await instance.torusSp.customAuthInstance.torus.retrieveShares({
160 | idToken,
161 | nodePubkeys: nodeDetails.torusNodePub,
162 | verifier: "torus-test-health",
163 | verifierParams: {
164 | verifier_id: parsedToken.email,
165 | },
166 | endpoints: nodeDetails.torusNodeEndpoints,
167 | indexes: nodeDetails.torusIndexes,
168 | })
169 |
170 | }
171 |
172 | export class AsyncMemoryStorage implements IAsyncStorage {
173 | private _store: Record = {};
174 |
175 | async getItem(key: string): Promise {
176 | return this._store[key] || null;
177 | }
178 |
179 | async setItem(key: string, value: string): Promise {
180 | this._store[key] = value;
181 | }
182 | }
183 |
184 | export function bufferToElliptic(p: Buffer, ec = secp256k1): EllipticPoint {
185 | return ec.keyFromPublic(p).getPublic();
186 | }
187 |
188 |
189 | export function generateRandomEmail(): string {
190 | const username = stringGen(10);
191 | const domain = stringGen(5);
192 | const tld = stringGen(3);
193 | return `${username}@${domain}.${tld}`;
194 | }
195 |
196 |
--------------------------------------------------------------------------------
/tests/sfaImport.spec.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 | import test from "node:test";
3 |
4 | import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib";
5 | import { tssLib as tssLibFROST } from "@toruslabs/tss-frost-lib";
6 |
7 | import { AsyncStorage, MemoryStorage, TssLibType, TssShareType, WEB3AUTH_NETWORK } from "../src";
8 | import { criticalResetAccount, generateRandomEmail, loginWithSFA, newCoreKitLogInInstance } from "./setup";
9 |
10 | type ImportKeyTestVariable = {
11 | manualSync?: boolean;
12 | email: string;
13 | tssLib: TssLibType;
14 | };
15 |
16 | const storageInstance = new MemoryStorage();
17 | export const ImportSFATest = async (testVariable: ImportKeyTestVariable) => {
18 | async function newCoreKitInstance(email: string) {
19 | return newCoreKitLogInInstance({
20 | network: WEB3AUTH_NETWORK.DEVNET,
21 | manualSync: testVariable.manualSync,
22 | email: email,
23 | storageInstance,
24 | tssLib: testVariable.tssLib,
25 | registerExistingSFAKey: true,
26 | });
27 | }
28 |
29 | async function resetAccount(email: string) {
30 | const kit = await newCoreKitInstance(email);
31 | await criticalResetAccount(kit);
32 | await kit.logout();
33 | await new AsyncStorage(kit._storageKey, storageInstance).resetStore();
34 | }
35 |
36 | test(`import sfa key and recover tss key : ${testVariable.manualSync}`, async function (t) {
37 | const afterTest = async () => {
38 | await resetAccount(testVariable.email);
39 | };
40 | await t.test("#recover Tss key using 2 factors key, import tss key to new oauth login", async function () {
41 | const sfaResult = await loginWithSFA({
42 | network: WEB3AUTH_NETWORK.DEVNET,
43 | manualSync: testVariable.manualSync,
44 | email: testVariable.email,
45 | storageInstance,
46 | });
47 | const coreKitInstance = await newCoreKitInstance(testVariable.email);
48 |
49 | // Create 2 factors which will be used to recover tss key.
50 | const factorKeyDevice = await coreKitInstance.createFactor({
51 | shareType: TssShareType.DEVICE,
52 | });
53 |
54 | const factorKeyRecovery = await coreKitInstance.createFactor({
55 | shareType: TssShareType.RECOVERY,
56 | });
57 |
58 | if (testVariable.manualSync) {
59 | await coreKitInstance.commitChanges();
60 | }
61 |
62 | // Export key and logout.
63 | const exportedTssKey1 = await coreKitInstance._UNSAFE_exportTssKey();
64 | await coreKitInstance.logout();
65 |
66 | // Recover key from any two factors.
67 | const recoveredTssKey = await coreKitInstance._UNSAFE_recoverTssKey([factorKeyDevice, factorKeyRecovery]);
68 | assert.strictEqual(recoveredTssKey, exportedTssKey1);
69 | assert.strictEqual(sfaResult.finalKeyData.privKey,recoveredTssKey);
70 | // sfa key should be empty after import to mpc
71 | const sfaResult2 = await loginWithSFA({
72 | network: WEB3AUTH_NETWORK.DEVNET,
73 | manualSync: testVariable.manualSync,
74 | email: testVariable.email,
75 | storageInstance,
76 | });
77 | assert.strictEqual(sfaResult2.finalKeyData.privKey, "");
78 |
79 | const coreKitInstance2 = await newCoreKitInstance(testVariable.email);
80 | const tssKey2 = await coreKitInstance2._UNSAFE_exportTssKey();
81 | // core kit should have same sfa key which was imported before
82 | assert.strictEqual(tssKey2, exportedTssKey1);
83 | assert.strictEqual(sfaResult.finalKeyData.privKey, tssKey2);
84 |
85 |
86 | });
87 |
88 | await afterTest();
89 | t.afterEach(function () {
90 | return console.info("finished running recovery test");
91 | });
92 | t.after(function () {
93 | return console.info("finished running recovery tests");
94 | });
95 | });
96 | };
97 |
98 | const variable: ImportKeyTestVariable[] = [
99 | { manualSync: false, email: generateRandomEmail(), tssLib: tssLibDKLS },
100 | { manualSync: true, email: generateRandomEmail(), tssLib: tssLibDKLS },
101 | { manualSync: false, email: generateRandomEmail(), tssLib: tssLibFROST },
102 | ];
103 |
104 | variable.forEach(async (testVariable) => {
105 | await ImportSFATest(testVariable);
106 | });
107 |
--------------------------------------------------------------------------------
/torus.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require("@toruslabs/config/torus.config");
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@toruslabs/config/tsconfig.default.json",
3 | "compilerOptions": {
4 | "baseUrl": "."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | exports.baseConfig = {
4 | resolve: {
5 | alias: {
6 | "bn.js": path.resolve(__dirname, "node_modules/bn.js"),
7 | "js-sha3": path.resolve(__dirname, "node_modules/js-sha3"),
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------