Skip to content

Commit

Permalink
Merge pull request #1778 from Web3Auth/feat/mpc-signingProvider
Browse files Browse the repository at this point in the history
fix: mpc-signing provider
  • Loading branch information
chaitanyapotti authored Apr 15, 2024
2 parents 3dbc330 + 67026ac commit 7978abd
Show file tree
Hide file tree
Showing 19 changed files with 1,167 additions and 522 deletions.
233 changes: 123 additions & 110 deletions package-lock.json

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions packages/providers/ethereum-mpc-provider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Web3Auth Ethereum MPC Provider

[![npm version](https://img.shields.io/npm/v/@web3auth-mpc/ethereum-provider?label=%22%22)](https://www.npmjs.com/package/@web3auth-mpc/ethereum-provider/v/latest)
[![minzip](https://img.shields.io/bundlephobia/minzip/@web3auth-mpc/ethereum-provider?label=%22%22)](https://bundlephobia.com/result?p=@web3auth-mpc/ethereum-provider@latest)

> 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.
Web3Auth Ethereum Provider can be used to interact with wallet or connected EVM compatible chain using RPC calls. This is an EIP-1193 compatible JRPC provider. This package exposes a class `EthereumPrivateKeyProvider`, which accepts a `secp251k1` private key and returns `EIP1193` compatible provider, which can be used with various wallet sdks.

## 📖 Documentation

Read more about Web3Auth Ethereum Provider in the [official Web3Auth Documentation](https://web3auth.io/docs/sdk/web/providers/evm#getting-a-provider-from-any-secp256k1-private-key).

## 💡 Features
- Plug and Play, OAuth based Web3 Authentication Service
- Fully decentralized, non-custodial key infrastructure
- End to end Whitelabelable solution
- Threshold Cryptography based Key Reconstruction
- Multi Factor Authentication Setup & Recovery (Includes password, backup phrase, device factor editing/deletion etc)
- Support for WebAuthn & Passwordless Login
- Support for connecting to multiple wallets
- DApp Active Session Management

...and a lot more

## 🔗 Installation

```shell
npm install --save @web3auth/ethereum-mpc-provider
```

## 🩹 Example

```ts
import { EthereumPrivateKeyProvider } from "@web3auth/ethereum-mpc-provider";
import type { SafeEventEmitterProvider } from "@web3auth/base";
const signEthMessage = async (provider: SafeEventEmitterProvider): Promise<string> => {
const web3 = new Web3(provider as any);
const accounts = await web3.eth.getAccounts();
// hex message
const message = "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad";
const signature = await web3.eth.sign(message, accounts[0]);
return signature;
};

(async () => {
const provider = await EthereumPrivateKeyProvider.getProviderInstance({
chainConfig: {
rpcTarget: "https://polygon-rpc.com",
chainId: "0x89", // hex chain id
networkName: "matic",
ticker: "matic",
tickerName: "matic",
},
privKey: "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318",
});
const signedMessage = await signEthMessage(provider);
})();
```

Checkout the examples for your preferred blockchain and platform in our [examples repository](https://github.com/Web3Auth/examples/)

## 🌐 Demo

Checkout the [Web3Auth Demo](https://demo-app.web3auth.io/) to see how Web3Auth can be used in your application.

## 💬 Troubleshooting and Support

- 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.
- Checkout our [Troubleshooting Documentation Page](https://web3auth.io/docs/troubleshooting) to know the common issues and solutions.
- For Priority Support, please have a look at our [Pricing Page](https://web3auth.io/pricing.html) for the plan that suits your needs.
62 changes: 62 additions & 0 deletions packages/providers/ethereum-mpc-provider/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "@web3auth/ethereum-mpc-provider",
"version": "8.1.0",
"homepage": "https://github.com/Web3Auth/Web3Auth#readme",
"license": "ISC",
"main": "dist/ethereumMpcProvider.cjs.js",
"module": "dist/ethereumMpcProvider.esm.js",
"unpkg": "dist/ethereumMpcProvider.umd.min.js",
"jsdelivr": "dist/ethereumMpcProvider.umd.min.js",
"types": "dist/types/index.d.ts",
"author": "Torus Labs",
"scripts": {
"test": "mocha --config ../../.mocharc.json test/**.ts",
"test-debugger": "mocha --config ../../.mocharc.json --inspect-brk test/**.ts",
"dev": "torus-scripts start",
"build": "torus-scripts build",
"lint": "eslint --fix 'src/**/*.ts'",
"prepack": "npm run build",
"pre-commit": "lint-staged --cwd ."
},
"dependencies": {
"@ethereumjs/common": "^4.3.0",
"@ethereumjs/tx": "^5.3.0",
"@ethereumjs/util": "^9.0.3",
"@metamask/eth-sig-util": "^7.0.1",
"@metamask/rpc-errors": "^6.2.1",
"@toruslabs/base-controllers": "^5.5.5",
"@toruslabs/http-helpers": "^6.1.1",
"@toruslabs/openlogin-jrpc": "^8.1.0",
"@web3auth/base": "^8.1.0",
"@web3auth/base-provider": "^8.1.0",
"@web3auth/ethereum-provider": "^8.1.0"
},
"peerDependencies": {
"@babel/runtime": "7.x"
},
"files": [
"dist",
"src"
],
"lint-staged": {
"!(*d).ts": [
"eslint --cache --fix",
"prettier --write"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/Web3Auth/Web3Auth.git"
},
"bugs": {
"url": "https://github.com/Web3Auth/Web3Auth/issues"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=18.x",
"npm": ">=9.x"
}
}
1 change: 1 addition & 0 deletions packages/providers/ethereum-mpc-provider/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./providers";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./signingProviders";
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { providerErrors, rpcErrors } from "@metamask/rpc-errors";
import { JRPCEngine, JRPCMiddleware, providerFromEngine } from "@toruslabs/openlogin-jrpc";
import { CHAIN_NAMESPACES, CustomChainConfig, WalletInitializationError } from "@web3auth/base";
import { BaseProvider, BaseProviderConfig, BaseProviderState } from "@web3auth/base-provider";
import {
AddEthereumChainParameter,
createChainSwitchMiddleware,
createEthMiddleware,
createJsonRpcClient,
IChainSwitchHandlers,
TransactionFormatter,
} from "@web3auth/ethereum-provider";

import { createAccountMiddleware } from "../../rpc/ethRpcMiddlewares";
import { IAccountHandlers } from "../../rpc/interfaces";
import { getProviderHandlers } from "./signingUtils";

export interface EthereumSigningProviderConfig extends BaseProviderConfig {
chainConfig: CustomChainConfig;
}

export interface EthereumSigningProviderState extends BaseProviderState {
signMethods?: {
sign: (msgHash: Buffer, rawMsg?: Buffer) => Promise<{ v: number; r: Buffer; s: Buffer }>;
getPublic: () => Promise<Buffer>;
};
}
export class EthereumSigningProvider extends BaseProvider<
BaseProviderConfig,
EthereumSigningProviderState,
{
sign: (msgHash: Buffer, rawMsg?: Buffer) => Promise<{ v: number; r: Buffer; s: Buffer }>;
getPublic: () => Promise<Buffer>;
}
> {
readonly PROVIDER_CHAIN_NAMESPACE = CHAIN_NAMESPACES.EIP155;

constructor({ config, state }: { config: EthereumSigningProviderConfig; state?: EthereumSigningProviderState }) {
super({ config: { chainConfig: { ...config.chainConfig, chainNamespace: CHAIN_NAMESPACES.EIP155 } }, state });
}

public static getProviderInstance = async (params: {
signMethods: {
sign: (msgHash: Buffer, rawMsg?: Buffer) => Promise<{ v: number; r: Buffer; s: Buffer }>;
getPublic: () => Promise<Buffer>;
};
chainConfig: CustomChainConfig;
}): Promise<EthereumSigningProvider> => {
const providerFactory = new EthereumSigningProvider({ config: { chainConfig: params.chainConfig } });
await providerFactory.setupProvider(params.signMethods);
return providerFactory;
};

public async enable(): Promise<string[]> {
if (!this.state.signMethods)
throw providerErrors.custom({ message: "signMethods are not found in state, plz pass it in constructor state param", code: 4902 });
await this.setupProvider(this.state.signMethods);
return this._providerEngineProxy.request({ method: "eth_accounts" });
}

public async setupProvider({
sign,
getPublic,
}: {
sign: (msgHash: Buffer, rawMsg?: Buffer) => Promise<{ v: number; r: Buffer; s: Buffer }>;
getPublic: () => Promise<Buffer>;
}): Promise<void> {
const { chainNamespace } = this.config.chainConfig;
if (chainNamespace !== this.PROVIDER_CHAIN_NAMESPACE) throw WalletInitializationError.incompatibleChainNameSpace("Invalid chain namespace");
const txFormatter = new TransactionFormatter({
getProviderEngineProxy: this.getProviderEngineProxy.bind(this),
});
const providerHandlers = getProviderHandlers({
txFormatter,
sign,
getPublic,
getProviderEngineProxy: this.getProviderEngineProxy.bind(this),
});
const ethMiddleware = createEthMiddleware(providerHandlers);
const chainSwitchMiddleware = this.getChainSwitchMiddleware();
const engine = new JRPCEngine();
// Not a partial anymore because of checks in ctor
const { networkMiddleware } = createJsonRpcClient(this.config.chainConfig as CustomChainConfig);
engine.push(ethMiddleware);
engine.push(chainSwitchMiddleware);
engine.push(this.getAccountMiddleware());
engine.push(networkMiddleware);
const provider = providerFromEngine(engine);
this.updateProviderEngineProxy(provider);
await txFormatter.init();
await this.lookupNetwork();
}

public async updateAccount(params: {
signMethods: {
sign: (msgHash: Buffer, rawMsg?: Buffer) => Promise<{ v: number; r: Buffer; s: Buffer }>;
getPublic: () => Promise<Buffer>;
};
}): Promise<void> {
if (!this._providerEngineProxy) throw providerErrors.custom({ message: "Provider is not initialized", code: 4902 });
const currentSignMethods = this.state.signMethods;
if (!currentSignMethods) {
throw providerErrors.custom({ message: "signing methods are unavailable ", code: 4092 });
}
const currentPubKey = (await currentSignMethods.getPublic()).toString("hex");
const updatePubKey = (await params.signMethods.getPublic()).toString("hex");
if (currentPubKey !== updatePubKey) {
await this.setupProvider(params.signMethods);
this._providerEngineProxy.emit("accountsChanged", {
accounts: await this._providerEngineProxy.request<unknown, string[]>({ method: "eth_accounts" }),
});
}
}

public async switchChain(params: { chainId: string }): Promise<void> {
if (!this._providerEngineProxy) throw providerErrors.custom({ message: "Provider is not initialized", code: 4902 });
const chainConfig = this.getChainConfig(params.chainId);
this.update({
chainId: "loading",
});
this.configure({ chainConfig });
if (!this.state.signMethods) {
throw providerErrors.custom({ message: "sign methods are undefined", code: 4902 });
}
await this.setupProvider(this.state.signMethods);
}

protected async lookupNetwork(): Promise<string> {
if (!this._providerEngineProxy) throw providerErrors.custom({ message: "Provider is not initialized", code: 4902 });
const { chainId } = this.config.chainConfig;
if (!chainId) throw rpcErrors.invalidParams("chainId is required while lookupNetwork");
const network = await this._providerEngineProxy.request<string[], string>({
method: "net_version",
params: [],
});

if (parseInt(chainId, 16) !== parseInt(network, 10)) throw providerErrors.chainDisconnected(`Invalid network, net_version is: ${network}`);
if (this.state.chainId !== chainId) {
this.emit("chainChanged", chainId);
this.emit("connect", { chainId });
}
this.update({ chainId });
return network;
}

private getChainSwitchMiddleware(): JRPCMiddleware<unknown, unknown> {
const chainSwitchHandlers: IChainSwitchHandlers = {
addChain: async (params: AddEthereumChainParameter): Promise<void> => {
const { chainId, chainName, rpcUrls, blockExplorerUrls, nativeCurrency, iconUrls } = params;
this.addChain({
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId,
ticker: nativeCurrency?.symbol || "ETH",
tickerName: nativeCurrency?.name || "Ether",
displayName: chainName,
rpcTarget: rpcUrls[0],
blockExplorerUrl: blockExplorerUrls?.[0] || "",
decimals: nativeCurrency?.decimals || 18,
logo: iconUrls?.[0] || "https://images.toruswallet.io/eth.svg",
});
},
switchChain: async (params: { chainId: string }): Promise<void> => {
const { chainId } = params;
await this.switchChain({ chainId });
},
};
const chainSwitchMiddleware = createChainSwitchMiddleware(chainSwitchHandlers);
return chainSwitchMiddleware;
}

private getAccountMiddleware(): JRPCMiddleware<unknown, unknown> {
const accountHandlers: IAccountHandlers = {
updateSignMethods: async (params: {
signMethods: {
sign: (msgHash: Buffer, rawMsg?: Buffer) => Promise<{ v: number; r: Buffer; s: Buffer }>;
getPublic: () => Promise<Buffer>;
};
}): Promise<void> => {
await this.updateAccount(params);
},
};
return createAccountMiddleware(accountHandlers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./EthereumSigningProvider";
Loading

0 comments on commit 7978abd

Please sign in to comment.