From cdf148d3a3951cf34cece796fa777171fab2b4d4 Mon Sep 17 00:00:00 2001 From: Ansonhkg Date: Thu, 11 Apr 2024 23:08:53 +0100 Subject: [PATCH 1/6] feat: decouple eth wallet from provider --- .../test-pkp-eth-wallet-provider.mjs | 32 +++++++++++++++++++ .../src/lib/providers/EthWalletProvider.ts | 15 ++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs diff --git a/e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs b/e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs new file mode 100644 index 0000000000..981364228c --- /dev/null +++ b/e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs @@ -0,0 +1,32 @@ +// Test command: +// DEBUG=true NETWORK=cayenne yarn test:e2e:nodejs --filter=test-pkp-eth-wallet-provider.mjs + +import { client } from '../00-setup.mjs'; +import { LitAuthClient } from '@lit-protocol/lit-auth-client'; +import LITCONFIG from '../../lit.config.json' assert { type: 'json' }; +import { success, fail, testThis } from '../../tools/scripts/utils.mjs'; +import path from 'path'; +import { EthWalletProvider } from '@lit-protocol/lit-auth-client'; +import { ethers } from 'ethers'; + +export async function main() { + const PRIVATE_KEY = LITCONFIG.CONTROLLER_PRIVATE_KEY; + + const provider = new ethers.providers.JsonRpcProvider( + LITCONFIG.CHRONICLE_RPC + ); + + const wallet = new ethers.Wallet(PRIVATE_KEY, provider); + + const ethWalletProvider = new EthWalletProvider({ litNodeClient: client }); + + const authMethod = await ethWalletProvider.authenticate(wallet); + + if (!authMethod) { + fail('Unable to authenticate with eth wallet provider'); + } + + return success(`authMethod created: ${JSON.stringify(authMethod)}`); +} + +await testThis({ name: path.basename(import.meta.url), fn: main }); diff --git a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts index 96f4943ca9..c12f77ddae 100644 --- a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts +++ b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts @@ -22,8 +22,14 @@ export default class EthWalletProvider extends BaseProvider { */ public origin: string; - constructor(options: BaseProviderOptions & EthWalletProviderOptions) { - super(options); + constructor(options: EthWalletProviderOptions) { + super({ + rpcUrl: '', + relay: null as any, + litNodeClient: null, + ...options, + }); + try { this.domain = options.domain || window.location.hostname; this.origin = options.origin || window.location.origin; @@ -51,12 +57,11 @@ export default class EthWalletProvider extends BaseProvider { options?: EthWalletAuthenticateOptions ): Promise { const address = options?.address; - const signMessage = options?.signMessage; const chain = options?.chain || 'ethereum'; let authSig: AuthSig; - if (address && signMessage) { + if (address && options?.signMessage) { // Get chain ID or default to Ethereum mainnet const selectedChain = LIT_CHAINS[chain]; const chainId = selectedChain?.chainId ? selectedChain.chainId : 1; @@ -81,7 +86,7 @@ export default class EthWalletProvider extends BaseProvider { const toSign: string = message.prepareMessage(); // Use provided function to sign message - const signature = await signMessage(toSign); + const signature = await options.signMessage(toSign); authSig = { sig: signature, From 05d9494518757d839364cdd7a1c60e01b0a13ba9 Mon Sep 17 00:00:00 2001 From: Ansonhkg Date: Thu, 11 Apr 2024 23:28:32 +0100 Subject: [PATCH 2/6] feat(ethWalletProvider): add static method to get auth method --- .../test-pkp-eth-wallet-provider.mjs | 7 +- .../src/lib/providers/EthWalletProvider.ts | 79 +++++++++++++++---- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs b/e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs index 981364228c..450588eda2 100644 --- a/e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs +++ b/e2e-nodejs/group-pkp-auth-method/test-pkp-eth-wallet-provider.mjs @@ -18,9 +18,10 @@ export async function main() { const wallet = new ethers.Wallet(PRIVATE_KEY, provider); - const ethWalletProvider = new EthWalletProvider({ litNodeClient: client }); - - const authMethod = await ethWalletProvider.authenticate(wallet); + const authMethod = await EthWalletProvider.authenticate({ + signer: wallet, + litNodeClient: client, + }); if (!authMethod) { fail('Unable to authenticate with eth wallet provider'); diff --git a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts index c12f77ddae..7b8b83772c 100644 --- a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts +++ b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts @@ -9,7 +9,10 @@ import { LIT_CHAINS, AuthMethodType, LIT_ERROR } from '@lit-protocol/constants'; import { SiweMessage } from 'siwe'; import { ethers } from 'ethers'; import { BaseProvider } from './BaseProvider'; -import { checkAndSignAuthMessage } from '@lit-protocol/lit-node-client'; +import { + LitNodeClient, + checkAndSignAuthMessage, +} from '@lit-protocol/lit-node-client'; import { log, throwError } from '@lit-protocol/misc'; export default class EthWalletProvider extends BaseProvider { @@ -56,46 +59,92 @@ export default class EthWalletProvider extends BaseProvider { public async authenticate( options?: EthWalletAuthenticateOptions ): Promise { - const address = options?.address; - const chain = options?.chain || 'ethereum'; + return EthWalletProvider.authenticate({ + signer: options, + address: options?.address, + chain: options?.chain, + litNodeClient: this.litNodeClient, + expiration: options?.expiration, + domain: this.domain, + origin: this.origin, + }); + } + + /** + * Generate a wallet signature to use as an auth method + * + * @param {EthWalletAuthenticateOptions} options + * @param {string} [options.address] - Address to sign with + * @param {function} [options.signMessage] - Function to sign message with + * @param {string} [options.chain] - Name of chain to use for signature + * @param {number} [options.expiration] - When the auth signature expires + * @returns {Promise} - Auth method object containing the auth signature + * @static + * @memberof EthWalletProvider + * + * @example + * ```typescript + * const authMethod = await EthWalletProvider.authenticate({ + * signer: wallet, + * litNodeClient: client, + * }); + * ``` + */ + public static async authenticate({ + signer, + address, + chain, + litNodeClient, + expiration, + domain, + origin, + }: { + signer: ethers.Signer | ethers.Wallet | any; + litNodeClient: LitNodeClient; + address?: string; + chain?: string; + expiration?: string; + domain?: string; + origin?: string; + }): Promise { + chain = chain || 'ethereum'; let authSig: AuthSig; - if (address && options?.signMessage) { + if (signer?.signMessage) { // Get chain ID or default to Ethereum mainnet const selectedChain = LIT_CHAINS[chain]; const chainId = selectedChain?.chainId ? selectedChain.chainId : 1; // Get expiration or default to 24 hours - const expiration = - options?.expiration || - new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(); + expiration = + expiration || new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(); // Prepare Sign in with Ethereum message const preparedMessage: Partial = { - domain: this.domain, - uri: this.origin, - address: ethers.utils.getAddress(address), // convert to EIP-55 format or else SIWE complains + domain: domain || 'localhost', + uri: origin || 'http://localhost', + address: ethers.utils.getAddress(address || signer?.address), // convert to EIP-55 format or else SIWE complains version: '1', chainId, expirationTime: expiration, - nonce: this.litNodeClient.latestBlockhash!, + nonce: litNodeClient.latestBlockhash!, }; const message: SiweMessage = new SiweMessage(preparedMessage); const toSign: string = message.prepareMessage(); // Use provided function to sign message - const signature = await options.signMessage(toSign); + const signature = await signer.signMessage(toSign); authSig = { sig: signature, derivedVia: 'web3.eth.personal.sign', signedMessage: toSign, - address: address, + address: signer.address, }; } else { - if (!this.litNodeClient.latestBlockhash) { + if (!litNodeClient.latestBlockhash) { throwError({ message: 'Eth Blockhash is undefined. Try connecting to the Lit network again.', @@ -106,7 +155,7 @@ export default class EthWalletProvider extends BaseProvider { authSig = await checkAndSignAuthMessage({ chain, - nonce: this.litNodeClient.latestBlockhash!, + nonce: litNodeClient.latestBlockhash!, }); } From ce5f584aa0a4faf6882102a5996ecb4249f479dd Mon Sep 17 00:00:00 2001 From: Ansonhkg Date: Thu, 11 Apr 2024 23:31:35 +0100 Subject: [PATCH 3/6] fix(ethWalletProvider): constructor --- .../src/lib/providers/EthWalletProvider.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts index 7b8b83772c..cf0249d011 100644 --- a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts +++ b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts @@ -25,13 +25,8 @@ export default class EthWalletProvider extends BaseProvider { */ public origin: string; - constructor(options: EthWalletProviderOptions) { - super({ - rpcUrl: '', - relay: null as any, - litNodeClient: null, - ...options, - }); + constructor(options: EthWalletProviderOptions & BaseProviderOptions) { + super(options); try { this.domain = options.domain || window.location.hostname; From 1abcbb5c44209f569d5eb9e1567c191f9ed38d7a Mon Sep 17 00:00:00 2001 From: Ansonhkg Date: Fri, 12 Apr 2024 15:25:18 +0100 Subject: [PATCH 4/6] fix(EthWalletProvider): how address it retrieved from signer --- .../src/lib/providers/EthWalletProvider.ts | 20 ++++++++++++++++--- packages/types/src/lib/interfaces.ts | 6 ++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts index cf0249d011..0352ae3488 100644 --- a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts +++ b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts @@ -54,6 +54,11 @@ export default class EthWalletProvider extends BaseProvider { public async authenticate( options?: EthWalletAuthenticateOptions ): Promise { + + if (!options) { + throw new Error("Options are required to authenticate with EthWalletProvider."); + } + return EthWalletProvider.authenticate({ signer: options, address: options?.address, @@ -94,7 +99,7 @@ export default class EthWalletProvider extends BaseProvider { domain, origin, }: { - signer: ethers.Signer | ethers.Wallet | any; + signer: ethers.Signer | ethers.Wallet | EthWalletAuthenticateOptions; litNodeClient: LitNodeClient; address?: string; chain?: string; @@ -106,6 +111,15 @@ export default class EthWalletProvider extends BaseProvider { let authSig: AuthSig; + // convert to EIP-55 format or else SIWE complains + address = address || await signer?.getAddress!() || (signer as ethers.Wallet)?.address; + + if (!address) { + throw new Error(`Address is required to authenticate with EthWalletProvider. Cannot find it in signer or options.`); + } + + address = ethers.utils.getAddress(address); + if (signer?.signMessage) { // Get chain ID or default to Ethereum mainnet const selectedChain = LIT_CHAINS[chain]; @@ -119,7 +133,7 @@ export default class EthWalletProvider extends BaseProvider { const preparedMessage: Partial = { domain: domain || 'localhost', uri: origin || 'http://localhost', - address: ethers.utils.getAddress(address || signer?.address), // convert to EIP-55 format or else SIWE complains + address, version: '1', chainId, expirationTime: expiration, @@ -136,7 +150,7 @@ export default class EthWalletProvider extends BaseProvider { sig: signature, derivedVia: 'web3.eth.personal.sign', signedMessage: toSign, - address: signer.address, + address: address, }; } else { if (!litNodeClient.latestBlockhash) { diff --git a/packages/types/src/lib/interfaces.ts b/packages/types/src/lib/interfaces.ts index 69ef7dc0dc..7b567699bc 100644 --- a/packages/types/src/lib/interfaces.ts +++ b/packages/types/src/lib/interfaces.ts @@ -1527,6 +1527,12 @@ export interface EthWalletAuthenticateOptions extends BaseAuthenticateOptions { * When the auth signature expires */ expiration?: string; + + /** + * Get the address of the wallet + * @returns {string} - Ethereum wallet address + */ + getAddress?: () => string; } export interface OtpAuthenticateOptions extends BaseAuthenticateOptions { From 4f58aea35845b0614353cefa43616c762a446b2c Mon Sep 17 00:00:00 2001 From: Ansonhkg Date: Fri, 12 Apr 2024 15:26:28 +0100 Subject: [PATCH 5/6] fix: always check blochhash first --- .../src/lib/providers/EthWalletProvider.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts index 0352ae3488..541c4c12b0 100644 --- a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts +++ b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts @@ -107,6 +107,16 @@ export default class EthWalletProvider extends BaseProvider { domain?: string; origin?: string; }): Promise { + + if (!litNodeClient.latestBlockhash) { + throwError({ + message: + 'Eth Blockhash is undefined. Try connecting to the Lit network again.', + errorKind: LIT_ERROR.INVALID_ETH_BLOCKHASH.kind, + errorCode: LIT_ERROR.INVALID_ETH_BLOCKHASH.name, + }); + } + chain = chain || 'ethereum'; let authSig: AuthSig; @@ -153,15 +163,6 @@ export default class EthWalletProvider extends BaseProvider { address: address, }; } else { - if (!litNodeClient.latestBlockhash) { - throwError({ - message: - 'Eth Blockhash is undefined. Try connecting to the Lit network again.', - errorKind: LIT_ERROR.INVALID_ETH_BLOCKHASH.kind, - errorCode: LIT_ERROR.INVALID_ETH_BLOCKHASH.name, - }); - } - authSig = await checkAndSignAuthMessage({ chain, nonce: litNodeClient.latestBlockhash!, From d5289cf8d7a2f28ec25285cc1293af2183ae250d Mon Sep 17 00:00:00 2001 From: Ansonhkg Date: Fri, 12 Apr 2024 19:26:44 +0100 Subject: [PATCH 6/6] chore: prettier --- .../src/lib/providers/EthWalletProvider.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts index 541c4c12b0..d3502d2ff5 100644 --- a/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts +++ b/packages/lit-auth-client/src/lib/providers/EthWalletProvider.ts @@ -54,9 +54,10 @@ export default class EthWalletProvider extends BaseProvider { public async authenticate( options?: EthWalletAuthenticateOptions ): Promise { - if (!options) { - throw new Error("Options are required to authenticate with EthWalletProvider."); + throw new Error( + 'Options are required to authenticate with EthWalletProvider.' + ); } return EthWalletProvider.authenticate({ @@ -72,7 +73,7 @@ export default class EthWalletProvider extends BaseProvider { /** * Generate a wallet signature to use as an auth method - * + * * @param {EthWalletAuthenticateOptions} options * @param {string} [options.address] - Address to sign with * @param {function} [options.signMessage] - Function to sign message with @@ -81,7 +82,7 @@ export default class EthWalletProvider extends BaseProvider { * @returns {Promise} - Auth method object containing the auth signature * @static * @memberof EthWalletProvider - * + * * @example * ```typescript * const authMethod = await EthWalletProvider.authenticate({ @@ -107,7 +108,6 @@ export default class EthWalletProvider extends BaseProvider { domain?: string; origin?: string; }): Promise { - if (!litNodeClient.latestBlockhash) { throwError({ message: @@ -122,10 +122,15 @@ export default class EthWalletProvider extends BaseProvider { let authSig: AuthSig; // convert to EIP-55 format or else SIWE complains - address = address || await signer?.getAddress!() || (signer as ethers.Wallet)?.address; + address = + address || + (await signer?.getAddress!()) || + (signer as ethers.Wallet)?.address; if (!address) { - throw new Error(`Address is required to authenticate with EthWalletProvider. Cannot find it in signer or options.`); + throw new Error( + `Address is required to authenticate with EthWalletProvider. Cannot find it in signer or options.` + ); } address = ethers.utils.getAddress(address);