diff --git a/cypress/e2e/spec.cy.ts b/cypress/e2e/spec.cy.ts index 2f7c61a44f..093be6d346 100644 --- a/cypress/e2e/spec.cy.ts +++ b/cypress/e2e/spec.cy.ts @@ -549,7 +549,7 @@ describe('Lit Action', () => { url: 'https://cayenne.litgateway.com:7371/web/execute', data, }; - const res = await savedParams.litNodeClient.sendCommandToNode(reqBody); + const res = await savedParams.litNodeClient._sendCommandToNode(reqBody); expect(res).to.have.property('success', true); }); diff --git a/packages/core/src/lib/lit-core.ts b/packages/core/src/lib/lit-core.ts index 39dec183b8..2f8e2e6104 100644 --- a/packages/core/src/lib/lit-core.ts +++ b/packages/core/src/lib/lit-core.ts @@ -35,7 +35,6 @@ import { import { bootstrapLogManager, executeWithRetry, - getIpAddress, isBrowser, isNode, log, @@ -173,7 +172,7 @@ export class LitCore { } // -- set bootstrapUrls to match the network litNetwork unless it's set to custom - this.setCustomBootstrapUrls(); + this.#setCustomBootstrapUrls(); // -- set global variables globalThis.litConfig = this.config; @@ -437,9 +436,9 @@ export class LitCore { * that the client's configuration is always in sync with the current state of the * staking contract. * - * @returns {Promise} A promise that resolves when the listener is successfully set up. + * @returns { void } */ - private _listenForNewEpoch() { + #listenForNewEpoch(): void { // Check if we've already set up the listener to avoid duplicates if (this._stakingContractListener) { // Already listening, do nothing @@ -473,13 +472,14 @@ export class LitCore { if (globalThis.litConfig) delete globalThis.litConfig; } - _stopNetworkPolling() { + protected _stopNetworkPolling() { if (this._networkSyncInterval) { clearInterval(this._networkSyncInterval); this._networkSyncInterval = null; } } - _stopListeningForNewEpoch() { + + protected _stopListeningForNewEpoch() { if (this._stakingContract && this._stakingContractListener) { this._stakingContract.off('StateChanged', this._stakingContractListener); this._stakingContractListener = null; @@ -493,7 +493,7 @@ export class LitCore { * @returns { void } * */ - setCustomBootstrapUrls = (): void => { + #setCustomBootstrapUrls = (): void => { // -- validate if (this.config.litNetwork === 'custom') return; @@ -575,8 +575,8 @@ export class LitCore { await this._runHandshakeWithBootstrapUrls(); Object.assign(this, { ...coreNodeConfig, connectedNodes, serverKeys }); - this._scheduleNetworkSync(); - this._listenForNewEpoch(); + this.#scheduleNetworkSync(); + this.#listenForNewEpoch(); // FIXME: don't create global singleton; multiple instances of `core` should not all write to global // @ts-expect-error typeof globalThis is not defined. We're going to get rid of the global soon. @@ -607,7 +607,7 @@ export class LitCore { }): Promise { const challenge = this.getRandomHexString(64); - const handshakeResult = await this.handshakeWithNode( + const handshakeResult = await this.#handshakeWithNode( { url, challenge }, requestId ); @@ -697,7 +697,7 @@ export class LitCore { coreNodeConfig: CoreNodeConfig; }> { // -- handshake with each node - const requestId: string = this.getRequestId(); + const requestId: string = this.#getRequestId(); // track connectedNodes for the new handshake operation const connectedNodes = new Set(); @@ -819,7 +819,7 @@ export class LitCore { * We can remove this network sync code entirely if we refactor our code to fetch latest blockhash on-demand. * @private */ - private _scheduleNetworkSync() { + #scheduleNetworkSync() { if (this._networkSyncInterval) { clearInterval(this._networkSyncInterval); } @@ -862,7 +862,7 @@ export class LitCore { * @returns { string } * */ - getRequestId() { + #getRequestId(): string { return Math.random().toString(16).slice(2); } @@ -873,7 +873,7 @@ export class LitCore { * @returns { string } */ - getRandomHexString(size: number) { + getRandomHexString(size: number): string { return [...Array(size)] .map(() => Math.floor(Math.random() * 16).toString(16)) .join(''); @@ -887,7 +887,7 @@ export class LitCore { * @returns { Promise } * */ - handshakeWithNode = async ( + #handshakeWithNode = async ( params: HandshakeWithNode, requestId: string ): Promise => { @@ -909,7 +909,7 @@ export class LitCore { challenge: params.challenge, }; - return await this.sendCommandToNode({ + return await this._sendCommandToNode({ url: urlWithPath, data, requestId, @@ -967,7 +967,7 @@ export class LitCore { * @returns { Promise } * */ - sendCommandToNode = async ({ + protected _sendCommandToNode = async ({ url, data, requestId, @@ -1005,7 +1005,7 @@ export class LitCore { * @returns { Array> } * */ - getNodePromises = ( + protected _getNodePromises = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any callback: (url: string) => Promise // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -1030,7 +1030,7 @@ export class LitCore { * @returns The session signature for the given URL. * @throws An error if sessionSigs is not provided or if the session signature for the URL is not found. */ - getSessionSigByUrl = ({ + protected _getSessionSigByUrl = ({ sessionSigs, url, }: { @@ -1136,7 +1136,7 @@ export class LitCore { * @param { number } minNodeCount number of nodes we need valid results from in order to resolve * @returns { Promise | RejectedNodePromises> } */ - handleNodePromises = async ( + protected _handleNodePromises = async ( nodePromises: Promise[], requestId: string, minNodeCount: number @@ -1219,7 +1219,10 @@ export class LitCore { * @returns { void } * */ - _throwNodeError = (res: RejectedNodePromises, requestId: string): void => { + protected _throwNodeError = ( + res: RejectedNodePromises, + requestId: string + ): void => { if (res.error) { if ( ((res.error.errorCode && diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-expiration.test.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-expiration.test.ts new file mode 100644 index 0000000000..a345e1586e --- /dev/null +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-expiration.test.ts @@ -0,0 +1,17 @@ +import { getExpiration } from './get-expiration'; + +describe('getExpiration', () => { + it('should return the expiration date and time as an ISO string', () => { + // Arrange + const currentDate = new Date(); + const expectedExpiration = new Date( + currentDate.getTime() + 1000 * 60 * 60 * 24 + ).toISOString(); + + // Act + const result = getExpiration(); + + // Assert + expect(result).toBe(expectedExpiration); + }); +}); diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/get-expiration.ts b/packages/lit-node-client-nodejs/src/lib/helpers/get-expiration.ts new file mode 100644 index 0000000000..73eef94d9d --- /dev/null +++ b/packages/lit-node-client-nodejs/src/lib/helpers/get-expiration.ts @@ -0,0 +1,9 @@ +/** + * Returns the expiration date and time as an ISO string. + * The expiration is set to 24 hours from the current date and time. + * + * @returns {string} The expiration date and time as an ISO string. + */ +export const getExpiration = () => { + return new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(); +}; diff --git a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts index 59cdb0dca7..ca5df0f27a 100644 --- a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts +++ b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts @@ -1,6 +1,6 @@ import { computeAddress } from '@ethersproject/transactions'; import { BigNumber, ethers } from 'ethers'; -import { joinSignature, sha256 } from 'ethers/lib/utils'; +import { sha256 } from 'ethers/lib/utils'; import { SiweMessage } from 'siwe'; import { @@ -28,7 +28,6 @@ import { } from '@lit-protocol/constants'; import { LitCore, composeLitUrl } from '@lit-protocol/core'; import { - combineEcdsaShares, combineSignatureShares, encrypt, generateSessionKeyPair, @@ -70,7 +69,6 @@ import type { CustomNetwork, DecryptRequest, DecryptResponse, - EncryptRequest, EncryptResponse, ExecuteJsResponse, FormattedMultipleAccs, @@ -90,10 +88,10 @@ import type { SessionKeyPair, SessionSigningTemplate, SessionSigsMap, - SigShare, SignSessionKeyProp, SignSessionKeyResponse, Signature, + SigningAccessControlConditionRequest, SuccessNodePromises, ILitNodeClient, GetPkpSessionSigs, @@ -109,16 +107,14 @@ import type { SigResponse, EncryptSdkParams, GetLitActionSessionSigs, - GetSignSessionKeySharesProp, EncryptionSignRequest, - SigningAccessControlConditionRequest, JsonPKPClaimKeyRequest, } from '@lit-protocol/types'; import * as blsSdk from '@lit-protocol/bls-sdk'; import { normalizeJsParams } from './helpers/normalize-params'; import { encodeCode } from './helpers/encode-code'; -import { getFlattenShare, getSignatures } from './helpers/get-signatures'; +import { getSignatures } from './helpers/get-signatures'; import { removeDoubleQuotes } from './helpers/remove-double-quotes'; import { parseAsJsonOrString } from './helpers/parse-as-json-or-string'; import { getClaimsList } from './helpers/get-claims-list'; @@ -127,6 +123,7 @@ import { normalizeArray } from './helpers/normalize-array'; import { parsePkpSignResponse } from './helpers/parse-pkp-sign-response'; import { getBlsSignatures } from './helpers/get-bls-signatures'; import { processLitActionResponseStrategy } from './helpers/process-lit-action-response-strategy'; +import { getExpiration } from './helpers/get-expiration'; export class LitNodeClientNodeJs extends LitCore @@ -151,186 +148,196 @@ export class LitNodeClientNodeJs } } - // ========== Rate Limit NFT ========== + // ========== Private Methods ========== + /** + * Handles the authentication callback and updates the storage item with the authentication signature. + * @param authCallbackParams - The parameters required for the authentication callback. + * @param authCallback - The optional authentication callback function. + * @returns A promise that resolves to the authentication signature. + * @throws An error if no default authentication callback is provided. + */ + #authCallbackAndUpdateStorageItem = async ({ + authCallbackParams, + authCallback, + }: { + authCallbackParams: AuthCallbackParams; + authCallback?: AuthCallback; + }): Promise => { + let authSig: AuthSig; - // TODO: Add support for browser feature/lit-2321-js-sdk-add-browser-support-for-createCapacityDelegationAuthSig - createCapacityDelegationAuthSig = async ( - params: CapacityCreditsReq - ): Promise => { - // -- validate - if (!params.dAppOwnerWallet) { - throw new Error('dAppOwnerWallet must exist'); + if (authCallback) { + authSig = await authCallback(authCallbackParams); + } else { + if (!this.defaultAuthCallback) { + return throwError({ + message: 'No default auth callback provided', + errorKind: LIT_ERROR.PARAMS_MISSING_ERROR.kind, + errorCode: LIT_ERROR.PARAMS_MISSING_ERROR.name, + }); + } + authSig = await this.defaultAuthCallback(authCallbackParams); } - // Useful log for debugging - if (!params.delegateeAddresses || params.delegateeAddresses.length === 0) { - log( - `[createCapacityDelegationAuthSig] 'delegateeAddresses' is an empty array. It means that no body can use it. However, if the 'delegateeAddresses' field is omitted, It means that the capability will not restrict access based on delegatee list, but it may still enforce other restrictions such as usage limits (uses) and specific NFT IDs (nft_id).` - ); + // (TRY) to set walletSig to local storage + const storeNewWalletSigOrError = setStorageItem( + LOCAL_STORAGE_KEYS.WALLET_SIGNATURE, + JSON.stringify(authSig) + ); + if (storeNewWalletSigOrError.type === EITHER_TYPE.SUCCESS) { + return authSig; } - // -- This is the owner address who holds the Capacity Credits NFT token and wants to delegate its - // usage to a list of delegatee addresses - const dAppOwnerWalletAddress = ethers.utils.getAddress( - await params.dAppOwnerWallet.getAddress() + // Setting local storage failed, try to remove the item key. + console.warn( + `Unable to store walletSig in local storage. Not a problem. Continuing to remove item key...` ); - - // -- if it's not ready yet, then connect - if (!this.ready) { - await this.connect(); + const removeWalletSigOrError = removeStorageItem( + LOCAL_STORAGE_KEYS.WALLET_SIGNATURE + ); + if (removeWalletSigOrError.type === EITHER_TYPE.ERROR) { + console.warn( + `Unable to remove walletSig in local storage. Not a problem. Continuing...` + ); } - const nonce = await this.getLatestBlockhash(); - const siweMessage = await createSiweMessageWithCapacityDelegation({ - uri: 'lit:capability:delegation', - litNodeClient: this, - walletAddress: dAppOwnerWalletAddress, - nonce: nonce, - expiration: params.expiration, - domain: params.domain, - statement: params.statement, - - // -- capacity delegation specific configuration - uses: params.uses, - delegateeAddresses: params.delegateeAddresses, - capacityTokenId: params.capacityTokenId, - }); - - const authSig = await generateAuthSig({ - signer: params.dAppOwnerWallet, - toSign: siweMessage, - }); - - return { capacityDelegationAuthSig: authSig }; + return authSig; }; - - // ========== Scoped Class Helpers ========== - /** * - * we need to send jwt params iat (issued at) and exp (expiration) because the nodes may have different wall clock times, the nodes will verify that these params are withing a grace period + * Check if a session key needs to be resigned. These are the scenarios where a session key needs to be resigned: + * 1. The authSig.sig does not verify successfully against the authSig.signedMessage + * 2. The authSig.signedMessage.uri does not match the sessionKeyUri + * 3. The authSig.signedMessage does not contain at least one session capability object * */ - getJWTParams = () => { - const now = Date.now(); - const iat = Math.floor(now / 1000); - const exp = iat + 12 * 60 * 60; // 12 hours in seconds + #checkNeedToResignSessionKey = async ({ + authSig, + sessionKeyUri, + resourceAbilityRequests, + }: { + authSig: AuthSig; + sessionKeyUri: any; + resourceAbilityRequests: LitResourceAbilityRequest[]; + }): Promise => { + const authSigSiweMessage = new SiweMessage(authSig.signedMessage); - return { iat, exp }; - }; + try { + await authSigSiweMessage.validate(authSig.sig); + } catch (e) { + console.debug('Need retry because verify failed', e); + return true; + } - // ==================== SESSIONS ==================== - /** - * Try to get the session key in the local storage, - * if not, generates one. - * @return { SessionKeyPair } session key pair - */ - getSessionKey = (): SessionKeyPair => { - const storageKey = LOCAL_STORAGE_KEYS.SESSION_KEY; - const storedSessionKeyOrError = getStorageItem(storageKey); + // make sure the sig is for the correct session key + if (authSigSiweMessage.uri !== sessionKeyUri) { + console.debug('Need retry because uri does not match'); + return true; + } + // make sure the authSig contains at least one resource. if ( - storedSessionKeyOrError.type === EITHER_TYPE.ERROR || - !storedSessionKeyOrError.result || - storedSessionKeyOrError.result === '' + !authSigSiweMessage.resources || + authSigSiweMessage.resources.length === 0 ) { - console.warn( - `Storage key "${storageKey}" is missing. Not a problem. Contiune...` - ); + console.debug('Need retry because empty resources'); + return true; + } - // Generate new one - const newSessionKey = generateSessionKeyPair(); + // make sure the authSig contains session capabilities that can be parsed. + // TODO: we currently only support the first resource being a session capability object. + const authSigSessionCapabilityObject = decode( + authSigSiweMessage.resources[0] + ); - // (TRY) to set to local storage - try { - localStorage.setItem(storageKey, JSON.stringify(newSessionKey)); - } catch (e) { - log( - `[getSessionKey] Localstorage not available.Not a problem.Contiune...` - ); + // make sure the authSig session capability object describes capabilities that are equal or greater than + // the abilities requested against the resources in the resource ability requests. + for (const resourceAbilityRequest of resourceAbilityRequests) { + if ( + !authSigSessionCapabilityObject.verifyCapabilitiesForResource( + resourceAbilityRequest.resource, + resourceAbilityRequest.ability + ) + ) { + console.debug('Need retry because capabilities do not match', { + authSigSessionCapabilityObject, + resourceAbilityRequest, + }); + return true; } - - return newSessionKey; - } else { - return JSON.parse(storedSessionKeyOrError.result as string); } - }; + return false; + }; /** - * Check if a given object is of type SessionKeyPair. + * Decrypts the ciphertext using the provided signature shares. * - * @param obj - The object to check. - * @returns True if the object is of type SessionKeyPair. + * @param networkPubKey - The network public key. + * @param identityParam - The identity parameter. + * @param ciphertext - The ciphertext to decrypt. + * @param signatureShares - An array of signature shares. + * @returns The decrypted data as a Uint8Array. */ - isSessionKeyPair(obj: any): obj is SessionKeyPair { - return ( - typeof obj === 'object' && - 'publicKey' in obj && - 'secretKey' in obj && - typeof obj.publicKey === 'string' && - typeof obj.secretKey === 'string' - ); - } + #decryptWithSignatureShares = ( + networkPubKey: string, + identityParam: Uint8Array, + ciphertext: string, + signatureShares: NodeBlsSigningShare[] + ): Uint8Array => { + const sigShares = signatureShares.map((s: any) => s.signatureShare); + return verifyAndDecryptWithSignatureShares( + networkPubKey, + identityParam, + ciphertext, + sigShares + ); + }; /** - * Generates wildcard capability for each of the LIT resources - * specified. - * @param litResources is an array of LIT resources - * @param addAllCapabilities is a boolean that specifies whether to add all capabilities for each resource + * Checks if the given response is a success node promise. + * @private + * @param res - The response object to check. + * @returns A boolean indicating whether the response is a success node promise. + * @template T - The type of the success node promise. */ - static async generateSessionCapabilityObjectWithWildcards( - litResources: ILitResource[], - addAllCapabilities?: boolean, - rateLimitAuthSig?: AuthSig - ): Promise { - const sessionCapabilityObject = new RecapSessionCapabilityObject({}, []); - - // disable for now - const _addAllCapabilities = addAllCapabilities ?? false; - - if (_addAllCapabilities) { - for (const litResource of litResources) { - sessionCapabilityObject.addAllCapabilitiesForResource(litResource); - } - } - - if (rateLimitAuthSig) { - throw new Error('Not implemented yet.'); - // await sessionCapabilityObject.addRateLimitAuthSig(rateLimitAuthSig); - } - - return sessionCapabilityObject; - } - - // backward compatibility - async generateSessionCapabilityObjectWithWildcards( - litResources: ILitResource[] - ): Promise { - return await LitNodeClientNodeJs.generateSessionCapabilityObjectWithWildcards( - litResources - ); - } - + #isSuccessNodePromises = (res: any): res is SuccessNodePromises => { + return res.success === true; + }; /** - * - * Get expiration for session default time is 1 day / 24 hours - * + * Generates an identity parameter for encryption based on the provided conditions and private data. + * @param hashOfConditionsStr - The hash of the conditions string. + * @param hashOfPrivateDataStr - The hash of the private data string. + * @returns The generated identity parameter for encryption. */ - static getExpiration = () => { - return new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(); + #getIdentityParamForEncryption = ( + hashOfConditionsStr: string, + hashOfPrivateDataStr: string + ): string => { + return new LitAccessControlConditionResource( + `${hashOfConditionsStr}/${hashOfPrivateDataStr}` + ).getResourceKey(); }; + /** + * we need to send jwt params iat (issued at) and exp (expiration) because the nodes may have + * different wall clock times, the nodes will verify that these params are withing a grace period + * + * @returns { { iat: number, exp: number } } the jwt params + */ + #getJWTParams = (): { + iat: number; + exp: number; + } => { + const now = Date.now(); + const iat = Math.floor(now / 1000); + const exp = iat + 12 * 60 * 60; // 12 hours in seconds - // backward compatibility - getExpiration = () => { - return LitNodeClientNodeJs.getExpiration(); + return { iat, exp }; }; - /** * * Get the signature from local storage, if not, generates one * */ - getWalletSig = async ({ + #getWalletSig = async ({ authNeededCallback, chain, sessionCapabilityObject, @@ -447,123 +454,6 @@ export class LitNodeClientNodeJs log('getWalletSig - flow 3'); return walletSig!; }; - - #authCallbackAndUpdateStorageItem = async ({ - authCallbackParams, - authCallback, - }: { - authCallbackParams: AuthCallbackParams; - authCallback?: AuthCallback; - }): Promise => { - let authSig: AuthSig; - - if (authCallback) { - authSig = await authCallback(authCallbackParams); - } else { - if (!this.defaultAuthCallback) { - return throwError({ - message: 'No default auth callback provided', - errorKind: LIT_ERROR.PARAMS_MISSING_ERROR.kind, - errorCode: LIT_ERROR.PARAMS_MISSING_ERROR.name, - }); - } - authSig = await this.defaultAuthCallback(authCallbackParams); - } - - // (TRY) to set walletSig to local storage - const storeNewWalletSigOrError = setStorageItem( - LOCAL_STORAGE_KEYS.WALLET_SIGNATURE, - JSON.stringify(authSig) - ); - if (storeNewWalletSigOrError.type === EITHER_TYPE.SUCCESS) { - return authSig; - } - - // Setting local storage failed, try to remove the item key. - console.warn( - `Unable to store walletSig in local storage. Not a problem. Continuing to remove item key...` - ); - const removeWalletSigOrError = removeStorageItem( - LOCAL_STORAGE_KEYS.WALLET_SIGNATURE - ); - if (removeWalletSigOrError.type === EITHER_TYPE.ERROR) { - console.warn( - `Unable to remove walletSig in local storage. Not a problem. Continuing...` - ); - } - - return authSig; - }; - - /** - * - * Check if a session key needs to be resigned. These are the scenarios where a session key needs to be resigned: - * 1. The authSig.sig does not verify successfully against the authSig.signedMessage - * 2. The authSig.signedMessage.uri does not match the sessionKeyUri - * 3. The authSig.signedMessage does not contain at least one session capability object - * - */ - checkNeedToResignSessionKey = async ({ - authSig, - sessionKeyUri, - resourceAbilityRequests, - }: { - authSig: AuthSig; - sessionKeyUri: any; - resourceAbilityRequests: LitResourceAbilityRequest[]; - }): Promise => { - const authSigSiweMessage = new SiweMessage(authSig.signedMessage); - - try { - await authSigSiweMessage.validate(authSig.sig); - } catch (e) { - console.debug('Need retry because verify failed', e); - return true; - } - - // make sure the sig is for the correct session key - if (authSigSiweMessage.uri !== sessionKeyUri) { - console.debug('Need retry because uri does not match'); - return true; - } - - // make sure the authSig contains at least one resource. - if ( - !authSigSiweMessage.resources || - authSigSiweMessage.resources.length === 0 - ) { - console.debug('Need retry because empty resources'); - return true; - } - - // make sure the authSig contains session capabilities that can be parsed. - // TODO: we currently only support the first resource being a session capability object. - const authSigSessionCapabilityObject = decode( - authSigSiweMessage.resources[0] - ); - - // make sure the authSig session capability object describes capabilities that are equal or greater than - // the abilities requested against the resources in the resource ability requests. - for (const resourceAbilityRequest of resourceAbilityRequests) { - if ( - !authSigSessionCapabilityObject.verifyCapabilitiesForResource( - resourceAbilityRequest.resource, - resourceAbilityRequest.ability - ) - ) { - console.debug('Need retry because capabilities do not match', { - authSigSessionCapabilityObject, - resourceAbilityRequest, - }); - return true; - } - } - - return false; - }; - - // ==================== API Calls to Nodes ==================== - /** * * Combine Shares from network public key set and signature shares @@ -573,7 +463,7 @@ export class LitNodeClientNodeJs * @returns { string } final JWT (convert the sig to base64 and append to the jwt) * */ - combineSharesAndGetJWT = ( + #combineSharesAndGetJWT = ( signatureShares: NodeBlsSigningShare[], requestId: string = '' ): string => { @@ -613,104 +503,274 @@ export class LitNodeClientNodeJs return finalJwt; }; - - #decryptWithSignatureShares = ( - networkPubKey: string, - identityParam: Uint8Array, - ciphertext: string, - signatureShares: NodeBlsSigningShare[] - ): Uint8Array => { - const sigShares = signatureShares.map((s: any) => s.signatureShare); - - return verifyAndDecryptWithSignatureShares( - networkPubKey, - identityParam, - ciphertext, - sigShares - ); + /** + * + * Get Session Key URI eg. lit:session:0x1234 + * + * @param publicKey is the public key of the session key + * @returns { string } the session key uri + */ + #getSessionKeyUri = (publicKey: string): string => { + return LIT_SESSION_KEY_URI + publicKey; }; - - // ========== Promise Handlers ========== - getIpfsId = async ({ - dataToHash, - sessionSigs, - }: { - dataToHash: string; - sessionSigs: SessionSigsMap; - debug?: boolean; - }) => { - const res = await this.executeJs({ - ipfsId: LIT_ACTION_IPFS_HASH, - sessionSigs, - jsParams: { - dataToHash, - }, - }).catch((e) => { - logError('Error getting IPFS ID', e); - throw e; + /** + * Generates a promise by sending a command to the Lit node + * + * @param url - The URL to send the command to. + * @param params - The parameters to include in the command. + * @param requestId - The ID of the request. + * @returns A promise that resolves with the response from the server. + */ + #generatePromise = async ( + url: string, + params: any, + requestId: string + ): Promise => { + return await this._sendCommandToNode({ + url, + data: params, + requestId, }); - - let data; - - if (typeof res.response === 'string') { - try { - data = JSON.parse(res.response).res; - } catch (e) { - data = res.response; - } - } - - if (!data.success) { - logError('Error getting IPFS ID', data.data); - } - - return data.data; }; + // ========== Rate Limit NFT ========== + // TODO: Add support for browser feature/lit-2321-js-sdk-add-browser-support-for-createCapacityDelegationAuthSig /** - * Run lit action on a single deterministicly selected node. It's important that the nodes use the same deterministic selection algorithm. - * - * Lit Action: dataToHash -> IPFS CID - * QmUjX8MW6StQ7NKNdaS6g4RMkvN5hcgtKmEi8Mca6oX4t3 - * - * @param { ExecuteJsProps } params - * - * @returns { Promise | RejectedNodePromises> } + * Creates a capacity delegation authSig. * + * @param params - The parameters for creating the capacity delegation authSig. + * @returns A promise that resolves to the capacity delegation authSig. + * @throws An error if the dAppOwnerWallet is not provided. */ - runOnTargetedNodes = async ( - params: JsonExecutionSdkParamsTargetNode - ): Promise< - SuccessNodePromises | RejectedNodePromises - > => { - log('running runOnTargetedNodes:', params.targetNodeRange); + createCapacityDelegationAuthSig = async ( + params: CapacityCreditsReq + ): Promise => { + // -- validate + if (!params.dAppOwnerWallet) { + throw new Error('dAppOwnerWallet must exist'); + } - if (!params.targetNodeRange) { - return throwError({ - message: 'targetNodeRange is required', - errorKind: LIT_ERROR.INVALID_PARAM_TYPE.kind, - errorCode: LIT_ERROR.INVALID_PARAM_TYPE.name, - }); + // Useful log for debugging + if (!params.delegateeAddresses || params.delegateeAddresses.length === 0) { + log( + `[createCapacityDelegationAuthSig] 'delegateeAddresses' is an empty array. It means that no body can use it. However, if the 'delegateeAddresses' field is omitted, It means that the capability will not restrict access based on delegatee list, but it may still enforce other restrictions such as usage limits (uses) and specific NFT IDs (nft_id).` + ); } - // determine which node to run on - const ipfsId = await this.getIpfsId({ - dataToHash: params.code!, - sessionSigs: params.sessionSigs, - }); + // -- This is the owner address who holds the Capacity Credits NFT token and wants to delegate its + // usage to a list of delegatee addresses + const dAppOwnerWalletAddress = ethers.utils.getAddress( + await params.dAppOwnerWallet.getAddress() + ); - // select targetNodeRange number of random index of the bootstrapUrls.length - const randomSelectedNodeIndexes: number[] = []; + // -- if it's not ready yet, then connect + if (!this.ready) { + await this.connect(); + } - let nodeCounter = 0; + const nonce = await this.getLatestBlockhash(); + const siweMessage = await createSiweMessageWithCapacityDelegation({ + uri: 'lit:capability:delegation', + litNodeClient: this, + walletAddress: dAppOwnerWalletAddress, + nonce: nonce, + expiration: params.expiration, + domain: params.domain, + statement: params.statement, - while (randomSelectedNodeIndexes.length < params.targetNodeRange) { - const str = `${nodeCounter}:${ipfsId.toString()}`; - const cidBuffer = Buffer.from(str); - const hash = sha256(cidBuffer); - const hashAsNumber = BigNumber.from(hash); + // -- capacity delegation specific configuration + uses: params.uses, + delegateeAddresses: params.delegateeAddresses, + capacityTokenId: params.capacityTokenId, + }); - const nodeIndex = hashAsNumber + const authSig = await generateAuthSig({ + signer: params.dAppOwnerWallet, + toSign: siweMessage, + }); + + return { capacityDelegationAuthSig: authSig }; + }; + + // ==================== SESSIONS ==================== + /** + * Try to get the session key in the local storage, + * if not, generates one. + * @return { SessionKeyPair } session key pair + */ + getSessionKey = (): SessionKeyPair => { + const storageKey = LOCAL_STORAGE_KEYS.SESSION_KEY; + const storedSessionKeyOrError = getStorageItem(storageKey); + + if ( + storedSessionKeyOrError.type === EITHER_TYPE.ERROR || + !storedSessionKeyOrError.result || + storedSessionKeyOrError.result === '' + ) { + console.warn( + `Storage key "${storageKey}" is missing. Not a problem. Contiune...` + ); + + // Generate new one + const newSessionKey = generateSessionKeyPair(); + + // (TRY) to set to local storage + try { + localStorage.setItem(storageKey, JSON.stringify(newSessionKey)); + } catch (e) { + log( + `[getSessionKey] Localstorage not available.Not a problem.Contiune...` + ); + } + + return newSessionKey; + } else { + return JSON.parse(storedSessionKeyOrError.result as string); + } + }; + + /** + * Check if a given object is of type SessionKeyPair. + * + * @param obj - The object to check. + * @returns True if the object is of type SessionKeyPair. + */ + isSessionKeyPair(obj: any): obj is SessionKeyPair { + return ( + typeof obj === 'object' && + 'publicKey' in obj && + 'secretKey' in obj && + typeof obj.publicKey === 'string' && + typeof obj.secretKey === 'string' + ); + } + + /** + * Generates wildcard capability for each of the LIT resources + * specified. + * @param litResources is an array of LIT resources + * @param addAllCapabilities is a boolean that specifies whether to add all capabilities for each resource + */ + static async generateSessionCapabilityObjectWithWildcards( + litResources: ILitResource[], + addAllCapabilities?: boolean + ): Promise { + const sessionCapabilityObject = new RecapSessionCapabilityObject({}, []); + + // disable for now + const _addAllCapabilities = addAllCapabilities ?? false; + + if (_addAllCapabilities) { + for (const litResource of litResources) { + sessionCapabilityObject.addAllCapabilitiesForResource(litResource); + } + } + + return sessionCapabilityObject; + } + + // backward compatibility + async generateSessionCapabilityObjectWithWildcards( + litResources: ILitResource[] + ): Promise { + return await LitNodeClientNodeJs.generateSessionCapabilityObjectWithWildcards( + litResources + ); + } + + /** + * Get expiration for session default time is 1 day / 24 hours + */ + static getExpiration = () => { + return getExpiration(); + }; + + // backward compatibility + getExpiration = () => { + return getExpiration(); + }; + + // ========== Promise Handlers ========== + getIpfsId = async ({ + dataToHash, + sessionSigs, + }: { + dataToHash: string; + sessionSigs: SessionSigsMap; + debug?: boolean; + }) => { + const res = await this.executeJs({ + ipfsId: LIT_ACTION_IPFS_HASH, + sessionSigs, + jsParams: { + dataToHash, + }, + }).catch((e) => { + logError('Error getting IPFS ID', e); + throw e; + }); + + let data; + + if (typeof res.response === 'string') { + try { + data = JSON.parse(res.response).res; + } catch (e) { + data = res.response; + } + } + + if (!data.success) { + logError('Error getting IPFS ID', data.data); + } + + return data.data; + }; + + /** + * Run lit action on a single deterministicly selected node. It's important that the nodes use the same deterministic selection algorithm. + * + * Lit Action: dataToHash -> IPFS CID + * QmUjX8MW6StQ7NKNdaS6g4RMkvN5hcgtKmEi8Mca6oX4t3 + * + * @param { ExecuteJsProps } params + * + * @returns { Promise | RejectedNodePromises> } + * + */ + runOnTargetedNodes = async ( + params: JsonExecutionSdkParamsTargetNode + ): Promise< + SuccessNodePromises | RejectedNodePromises + > => { + log('running runOnTargetedNodes:', params.targetNodeRange); + + if (!params.targetNodeRange) { + return throwError({ + message: 'targetNodeRange is required', + errorKind: LIT_ERROR.INVALID_PARAM_TYPE.kind, + errorCode: LIT_ERROR.INVALID_PARAM_TYPE.name, + }); + } + + // determine which node to run on + const ipfsId = await this.getIpfsId({ + dataToHash: params.code!, + sessionSigs: params.sessionSigs, + }); + + // select targetNodeRange number of random index of the bootstrapUrls.length + const randomSelectedNodeIndexes: number[] = []; + + let nodeCounter = 0; + + while (randomSelectedNodeIndexes.length < params.targetNodeRange) { + const str = `${nodeCounter}:${ipfsId.toString()}`; + const cidBuffer = Buffer.from(str); + const hash = sha256(cidBuffer); + const hashAsNumber = BigNumber.from(hash); + + const nodeIndex = hashAsNumber .mod(this.config.bootstrapUrls.length) .toNumber(); @@ -748,7 +808,7 @@ export class LitNodeClientNodeJs log(`running on node ${nodeIndex} at ${url}`); // -- choose the right signature - const sessionSig = this.getSessionSigByUrl({ + const sessionSig = this._getSessionSigByUrl({ sessionSigs: params.sessionSigs, url, }); @@ -761,7 +821,7 @@ export class LitNodeClientNodeJs // this return { url: string, data: JsonRequest } // const singleNodePromise = this.getJsExecutionShares(url, reqBody, id); - const singleNodePromise = this.sendCommandToNode({ + const singleNodePromise = this._sendCommandToNode({ url: url, data: params, requestId: id, @@ -770,7 +830,7 @@ export class LitNodeClientNodeJs nodePromises.push(singleNodePromise); } - const handledPromise = (await this.handleNodePromises( + const handledPromise = (await this._handleNodePromises( nodePromises, id, params.targetNodeRange @@ -793,511 +853,693 @@ export class LitNodeClientNodeJs /** * - * Get signatures from signed data + * Encrypt data using the LIT network public key. * - * @param { Array } signedData + * @param { EncryptSdkParams } params + * @param params.dataToEncrypt - The data to encrypt + * @param params.accessControlConditions - (optional) The access control conditions for the data + * @param params.evmContractConditions - (optional) The EVM contract conditions for the data + * @param params.solRpcConditions - (optional) The Solidity RPC conditions for the data + * @param params.unifiedAccessControlConditions - (optional) The unified access control conditions for the data * - * @returns { any } + * @return { Promise } The encrypted ciphertext and the hash of the data * + * @throws { Error } if the LIT node client is not ready + * @throws { Error } if the subnetPubKey is null */ - getSessionSignatures = (signedData: any[]): any => { - // -- prepare - const signatures: any = {}; - - // TOOD: get keys of signedData - const keys = Object.keys(signedData[0]); - - // removeExtraBackslashesAndQuotes - const sanitise = (str: string) => { - // Check if str is a string and remove extra backslashes - if (typeof str === 'string') { - // Remove backslashes - let newStr = str.replace(/\\+/g, ''); - // Remove leading and trailing double quotes - newStr = newStr.replace(/^"|"$/g, ''); - return newStr; - } - return str; - }; - - // -- execute - keys.forEach((key: any) => { - log('key:', key); - - const shares = signedData.map((r: any) => r[key]); - - log('shares:', shares); - - shares.sort((a: any, b: any) => a.shareIndex - b.shareIndex); - - const sigShares: SigShare[] = shares.map((s: any, index: number) => { - log('Original Share Struct:', s); - - const share = getFlattenShare(s); + encrypt = async (params: EncryptSdkParams): Promise => { + // ========== Validate Params ========== + // -- validate if it's ready + if (!this.ready) { + const message = + '6 LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; + throwError({ + message, + errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, + errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, + }); + } - log('share:', share); + // -- validate if this.subnetPubKey is null + if (!this.subnetPubKey) { + const message = 'subnetPubKey cannot be null'; + return throwError({ + message, + errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, + errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, + }); + } - if (!share) { - throw new Error('share is null or undefined'); - } + const paramsIsSafe = safeParams({ + functionName: 'encrypt', + params, + }); - if (!share.bigr) { - throw new Error( - `bigR is missing in share ${index}. share ${JSON.stringify(share)}` - ); - } + if (!paramsIsSafe) { + return throwError({ + message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + }); + } - const sanitisedBigR = sanitise(share.bigr); - const sanitisedSigShare = sanitise(share.publicKey); + // ========== Validate Access Control Conditions Schema ========== + await this.validateAccessControlConditionsSchema(params); - log('sanitisedBigR:', sanitisedBigR); - log('sanitisedSigShare:', sanitisedSigShare); + // ========== Hashing Access Control Conditions ========= + // hash the access control conditions + const hashOfConditions: ArrayBuffer | undefined = + await this.getHashedAccessControlConditions(params); - return { - sigType: share.sigType, - signatureShare: sanitise(share.signatureShare), - shareIndex: share.shareIndex, - bigR: sanitise(share.bigr), - publicKey: share.publicKey, - dataSigned: share.dataSigned, - siweMessage: share.siweMessage, - }; + if (!hashOfConditions) { + return throwError({ + message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, }); + } - log('getSessionSignatures - sigShares', sigShares); - - const sigType = mostCommonString(sigShares.map((s: any) => s.sigType)); - - // -- validate if this.networkPubKeySet is null - if (this.networkPubKeySet === null) { - throwError({ - message: 'networkPubKeySet cannot be null', - errorKind: LIT_ERROR.PARAM_NULL_ERROR.kind, - errorCode: LIT_ERROR.PARAM_NULL_ERROR.name, - }); - return; - } - - // -- validate if signature type is ECDSA - if ( - sigType !== LIT_CURVE.EcdsaCaitSith && - sigType !== LIT_CURVE.EcdsaK256 && - sigType !== LIT_CURVE.EcdsaCAITSITHP256 - ) { - throwError({ - message: `signature type is ${sigType} which is invalid`, - errorKind: LIT_ERROR.UNKNOWN_SIGNATURE_TYPE.kind, - errorCode: LIT_ERROR.UNKNOWN_SIGNATURE_TYPE.name, - }); - return; - } + const hashOfConditionsStr = uint8arrayToString( + new Uint8Array(hashOfConditions), + 'base16' + ); - const signature: any = combineEcdsaShares(sigShares); - if (!signature.r) { - throwError({ - message: 'siganture could not be combined', - errorKind: LIT_ERROR.UNKNOWN_SIGNATURE_ERROR.kind, - errorCode: LIT_ERROR.UNKNOWN_SIGNATURE_ERROR.name, - }); - } + // ========== Hashing Private Data ========== + // hash the private data + const hashOfPrivateData = await crypto.subtle.digest( + 'SHA-256', + params.dataToEncrypt + ); + const hashOfPrivateDataStr = uint8arrayToString( + new Uint8Array(hashOfPrivateData), + 'base16' + ); - const encodedSig = joinSignature({ - r: '0x' + signature.r, - s: '0x' + signature.s, - v: signature.recid, - }); + // ========== Assemble identity parameter ========== + const identityParam = this.#getIdentityParamForEncryption( + hashOfConditionsStr, + hashOfPrivateDataStr + ); - signatures[key] = { - ...signature, - signature: encodedSig, - publicKey: mostCommonString(sigShares.map((s: any) => s.publicKey)), - dataSigned: mostCommonString(sigShares.map((s: any) => s.dataSigned)), - siweMessage: mostCommonString(sigShares.map((s) => s.siweMessage)), - }; - }); + // ========== Encrypt ========== + const ciphertext = encrypt( + this.subnetPubKey, + params.dataToEncrypt, + uint8arrayFromString(identityParam, 'utf8') + ); - return signatures; + return { ciphertext, dataToEncryptHash: hashOfPrivateDataStr }; }; + /** ============================== SESSION ============================== */ /** + * Get session signatures for a set of resources * - * Get a single signature - * - * @param { Array } shareData from all node promises + * High level, how this works: + * 1. Generate or retrieve session key + * 2. Generate or retrieve the wallet signature of the session key + * 3. Sign the specific resources with the session key * - * @returns { string } signature + * Note: When generating session signatures for different PKPs or auth methods, + * be sure to call disconnectWeb3 to clear auth signatures stored in local storage * + * @param { GetSessionSigsProps } params + * + * @example + * + * ```ts + * import { LitPKPResource, LitActionResource } from "@lit-protocol/auth-helpers"; +import { LitAbility } from "@lit-protocol/types"; +import { logWithRequestId } from '../../../misc/src/lib/misc'; + +const resourceAbilityRequests = [ + { + resource: new LitPKPResource("*"), + ability: LitAbility.PKPSigning, + }, + { + resource: new LitActionResource("*"), + ability: LitAbility.LitActionExecution, + }, + ]; + * ``` */ - getSignature = async (shareData: any[], requestId: string): Promise => { - // R_x & R_y values can come from any node (they will be different per node), and will generate a valid signature - const R_x = shareData[0].local_x; - const R_y = shareData[0].local_y; + getSessionSigs = async ( + params: GetSessionSigsProps + ): Promise => { + // -- prepare + // Try to get it from local storage, if not generates one~ + const sessionKey = params.sessionKey ?? this.getSessionKey(); - const valid_shares = shareData.map((s: any) => s.signature_share); - const shares = JSON.stringify(valid_shares); + const sessionKeyUri = this.#getSessionKeyUri(sessionKey.publicKey); - await wasmECDSA.initWasmEcdsaSdk(); // init WASM - const signature = wasmECDSA.combine_signature(R_x, R_y, shares); - logWithRequestId(requestId, 'raw ecdsa sig', signature); + // First get or generate the session capability object for the specified resources. + const sessionCapabilityObject = params.sessionCapabilityObject + ? params.sessionCapabilityObject + : await this.generateSessionCapabilityObjectWithWildcards( + params.resourceAbilityRequests.map((r) => r.resource) + ); + const expiration = params.expiration || LitNodeClientNodeJs.getExpiration(); - return signature; - }; + if (!this.latestBlockhash) { + throwError({ + message: 'Eth Blockhash is undefined.', + errorKind: LIT_ERROR.INVALID_ETH_BLOCKHASH.kind, + errorCode: LIT_ERROR.INVALID_ETH_BLOCKHASH.name, + }); + } + const nonce = this.latestBlockhash!; - // ========== Scoped Business Logics ========== + // -- (TRY) to get the wallet signature + let authSig = await this.#getWalletSig({ + authNeededCallback: params.authNeededCallback, + chain: params.chain || 'ethereum', + sessionCapabilityObject, + switchChain: params.switchChain, + expiration: expiration, + sessionKey: sessionKey, + sessionKeyUri: sessionKeyUri, + nonce, - // Normalize the data to a basic array + // -- for recap + resourceAbilityRequests: params.resourceAbilityRequests, - // TODO: executeJsWithTargettedNodes - // if (formattedParams.targetNodeRange) { - // // FIXME: we should make this a separate function - // res = await this.runOnTargetedNodes(formattedParams); - // } + // -- optional fields + ...(params.litActionCode && { litActionCode: params.litActionCode }), + ...(params.litActionIpfsId && { + litActionIpfsId: params.litActionIpfsId, + }), + ...(params.jsParams && { jsParams: params.jsParams }), + }); - /** - * - * Execute JS on the nodes and combine and return any resulting signatures - * - * @param { JsonExecutionSdkParams } params - * - * @returns { ExecuteJsResponse } - * - */ - executeJs = async ( - params: JsonExecutionSdkParams - ): Promise => { - // ========== Validate Params ========== - if (!this.ready) { - const message = - '[executeJs] LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; + const needToResignSessionKey = await this.#checkNeedToResignSessionKey({ + authSig, + sessionKeyUri, + resourceAbilityRequests: params.resourceAbilityRequests, + }); - throwError({ - message, - errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, - errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, + // -- (CHECK) if we need to resign the session key + if (needToResignSessionKey) { + log('need to re-sign session key. Signing...'); + authSig = await this.#authCallbackAndUpdateStorageItem({ + authCallback: params.authNeededCallback, + authCallbackParams: { + chain: params.chain || 'ethereum', + statement: sessionCapabilityObject.statement, + resources: [sessionCapabilityObject.encodeAsSiweResource()], + switchChain: params.switchChain, + expiration, + sessionKey: sessionKey, + uri: sessionKeyUri, + nonce, + resourceAbilityRequests: params.resourceAbilityRequests, + + // -- optional fields + ...(params.litActionCode && { litActionCode: params.litActionCode }), + ...(params.litActionIpfsId && { + litActionIpfsId: params.litActionIpfsId, + }), + ...(params.jsParams && { jsParams: params.jsParams }), + }, }); } - const paramsIsSafe = safeParams({ - functionName: 'executeJs', - params: params, - }); - - if (!paramsIsSafe) { - return throwError({ - message: 'executeJs params are not valid', - errorKind: LIT_ERROR.INVALID_PARAM_TYPE.kind, - errorCode: LIT_ERROR.INVALID_PARAM_TYPE.name, + if ( + authSig.address === '' || + authSig.derivedVia === '' || + authSig.sig === '' || + authSig.signedMessage === '' + ) { + throwError({ + message: 'No wallet signature found', + errorKind: LIT_ERROR.WALLET_SIGNATURE_NOT_FOUND_ERROR.kind, + errorCode: LIT_ERROR.WALLET_SIGNATURE_NOT_FOUND_ERROR.name, }); + // @ts-ignore - we throw an error above, so below should never be reached + return; } - // Format the params - const formattedParams: JsonExecutionSdkParams = { - ...params, - ...(params.jsParams && { jsParams: normalizeJsParams(params.jsParams) }), - ...(params.code && { code: encodeCode(params.code) }), - }; + // ===== AFTER we have Valid Signed Session Key ===== + // - Let's sign the resources with the session key + // - 5 minutes is the default expiration for a session signature + // - Because we can generate a new session sig every time the user wants to access a resource without prompting them to sign with their wallet + const sessionExpiration = + expiration ?? new Date(Date.now() + 1000 * 60 * 5).toISOString(); - // ========== Get Node Promises ========== - // Handle promises for commands sent to Lit nodes - const wrapper = async ( - requestId: string - ): Promise | RejectedNodePromises> => { - const nodePromises = this.getNodePromises(async (url: string) => { - // -- choose the right signature - const sessionSig = this.getSessionSigByUrl({ - sessionSigs: formattedParams.sessionSigs, - url, - }); + const capabilities = params.capacityDelegationAuthSig + ? [ + ...(params.capabilityAuthSigs ?? []), + params.capacityDelegationAuthSig, + authSig, + ] + : [...(params.capabilityAuthSigs ?? []), authSig]; - const reqBody: JsonExecutionRequest = { - ...formattedParams, - authSig: sessionSig, - }; + const signingTemplate = { + sessionKey: sessionKey.publicKey, + resourceAbilityRequests: params.resourceAbilityRequests, + capabilities, + issuedAt: new Date().toISOString(), + expiration: sessionExpiration, + }; - const urlWithPath = composeLitUrl({ - url, - endpoint: LIT_ENDPOINT.EXECUTE_JS, - }); + const signatures: SessionSigsMap = {}; - return this.generatePromise(urlWithPath, reqBody, requestId); - }); + this.connectedNodes.forEach((nodeAddress: string) => { + const toSign: SessionSigningTemplate = { + ...signingTemplate, + nodeAddress, + }; - // -- resolve promises - const res = await this.handleNodePromises( - nodePromises, - requestId, - this.connectedNodes.size - ); + const signedMessage = JSON.stringify(toSign); - return res; - }; // wrapper end + const uint8arrayKey = uint8arrayFromString( + sessionKey.secretKey, + 'base16' + ); - // ========== Execute with Retry ========== - const res = await executeWithRetry< - RejectedNodePromises | SuccessNodePromises - >( - wrapper, - (error: any, requestId: string, isFinal: boolean) => { - logError('an error occured, attempting to retry operation'); - }, - this.config.retryTolerance - ); + const uint8arrayMessage = uint8arrayFromString(signedMessage, 'utf8'); + const signature = nacl.sign.detached(uint8arrayMessage, uint8arrayKey); - // ========== Handle Response ========== - const requestId = res.requestId; + signatures[nodeAddress] = { + sig: uint8arrayToString(signature, 'base16'), + derivedVia: 'litSessionSignViaNacl', + signedMessage: signedMessage, + address: sessionKey.publicKey, + algo: 'ed25519', + }; + }); - // -- case: promises rejected - if (!res.success) { - this._throwNodeError(res as RejectedNodePromises, requestId); - } + log('signatures:', signatures); - // -- case: promises success (TODO: check the keys of "values") - const responseData = (res as SuccessNodePromises).values; + return signatures; + }; - logWithRequestId( - requestId, - 'executeJs responseData from node : ', - JSON.stringify(responseData, null, 2) - ); + /** + * Retrieves the PKP sessionSigs. + * + * @param params - The parameters for retrieving the PKP sessionSigs. + * @returns A promise that resolves to the PKP sessionSigs. + * @throws An error if any of the required parameters are missing or if `litActionCode` and `ipfsId` exist at the same time. + */ + getPkpSessionSigs = async (params: GetPkpSessionSigs) => { + const chain = params?.chain || 'ethereum'; - // -- find the responseData that has the most common response - const mostCommonResponse = findMostCommonResponse( - responseData - ) as NodeShare; + const pkpSessionSigs = this.getSessionSigs({ + chain, + ...params, + authNeededCallback: async (props: AuthCallbackParams) => { + // -- validate + if (!props.expiration) { + throw new Error( + '[getPkpSessionSigs/callback] expiration is required' + ); + } - const responseFromStrategy: any = processLitActionResponseStrategy( - responseData, - params.responseStrategy ?? { strategy: 'leastCommon' } - ); - mostCommonResponse.response = responseFromStrategy; + if (!props.resources) { + throw new Error('[getPkpSessionSigs/callback]resources is required'); + } - const isSuccess = mostCommonResponse.success; - const hasSignedData = Object.keys(mostCommonResponse.signedData).length > 0; - const hasClaimData = Object.keys(mostCommonResponse.claimData).length > 0; + if (!props.resourceAbilityRequests) { + throw new Error( + '[getPkpSessionSigs/callback]resourceAbilityRequests is required' + ); + } - // -- we must also check for claim responses as a user may have submitted for a claim and signatures must be aggregated before returning - if (isSuccess && !hasSignedData && !hasClaimData) { - return mostCommonResponse as unknown as ExecuteJsResponse; - } + // lit action code and ipfs id cannot exist at the same time + if (props.litActionCode && props.litActionIpfsId) { + throw new Error( + '[getPkpSessionSigs/callback]litActionCode and litActionIpfsId cannot exist at the same time' + ); + } - // -- in the case where we are not signing anything on Lit action and using it as purely serverless function - if (!hasSignedData && !hasClaimData) { - return { - claims: {}, - signatures: null, - decryptions: [], - response: mostCommonResponse.response, - logs: mostCommonResponse.logs, - } as ExecuteJsNoSigningResponse; - } + /** + * We must provide an empty array for authMethods even if we are not using any auth methods. + * So that the nodes can serialize the request correctly. + */ + const authMethods = params.authMethods || []; - // ========== Extract shares from response data ========== + const response = await this.signSessionKey({ + sessionKey: props.sessionKey, + statement: props.statement || 'Some custom statement.', + authMethods: [...authMethods], + pkpPublicKey: params.pkpPublicKey, + expiration: props.expiration, + resources: props.resources, + chainId: 1, - // -- 1. combine signed data as a list, and get the signatures from it - const signedDataList = responseData.map((r) => { - return removeDoubleQuotes(r.signedData); - }); + // -- required fields + resourceAbilityRequests: props.resourceAbilityRequests, - logWithRequestId( - requestId, - 'signatures shares to combine: ', - signedDataList - ); + // -- optional fields + ...(props.litActionCode && { litActionCode: props.litActionCode }), + ...(props.litActionIpfsId && { + litActionIpfsId: props.litActionIpfsId, + }), + ...(props.jsParams && { jsParams: props.jsParams }), + }); - const signatures = getSignatures({ - requestId, - networkPubKeySet: this.networkPubKeySet, - minNodeCount: this.config.minNodeCount, - signedData: signedDataList, + return response.authSig; + }, }); - // -- 2. combine responses as a string, and parse it as JSON if possible - const parsedResponse = parseAsJsonOrString(mostCommonResponse.response); - - // -- 3. combine logs - const mostCommonLogs: string = mostCommonString( - responseData.map((r: NodeLog) => r.logs) - ); - - // -- 4. combine claims - const claimsList = getClaimsList(responseData); - const claims = claimsList.length > 0 ? getClaims(claimsList) : undefined; - - // ========== Result ========== - const returnVal: ExecuteJsResponse = { - claims, - signatures, - // decryptions: [], - response: parsedResponse, - logs: mostCommonLogs, - }; - - log('returnVal:', returnVal); - - return returnVal; + return pkpSessionSigs; }; /** - * Generates a promise by sending a command to the Lit node + * Retrieves session signatures specifically for Lit Actions. + * Unlike `getPkpSessionSigs`, this function requires either `litActionCode` or `litActionIpfsId`, and `jsParams` must be provided. * - * @param url - The URL to send the command to. - * @param params - The parameters to include in the command. - * @param requestId - The ID of the request. - * @returns A promise that resolves with the response from the server. + * @param params - The parameters required for retrieving the session signatures. + * @returns A promise that resolves with the session signatures. */ - generatePromise = async ( - url: string, - params: any, - requestId: string - ): Promise => { - return await this.sendCommandToNode({ - url, - data: params, - requestId, - }); + getLitActionSessionSigs = async (params: GetLitActionSessionSigs) => { + // Check if either litActionCode or litActionIpfsId is provided + if (!params.litActionCode && !params.litActionIpfsId) { + throw new Error( + "Either 'litActionCode' or 'litActionIpfsId' must be provided." + ); + } + + // Check if jsParams is provided + if (!params.jsParams) { + throw new Error("'jsParams' is required."); + } + + return this.getPkpSessionSigs(params); }; + /** ============================== END POINTS ============================== */ /** - * Use PKP to sign - * - * @param { JsonPkpSignSdkParams } params - * @param params.toSign - The data to sign - * @param params.pubKey - The public key to sign with - * @param params.sessionSigs - The session signatures to use - * @param params.authMethods - (optional) The auth methods to use + * Sign a session public key using a PKP, which generates an authSig. + * Endpoint: /web/sign_session_key endpoint. + * @returns {Object} An object containing the resulting signature. */ - pkpSign = async (params: JsonPkpSignSdkParams): Promise => { - // -- validate required params - const requiredParamKeys = ['toSign', 'pubKey']; + signSessionKey = async ( + params: SignSessionKeyProp + ): Promise => { + log(`[signSessionKey] params:`, params); - (requiredParamKeys as (keyof JsonPkpSignSdkParams)[]).forEach((key) => { - if (!params[key]) { - throwError({ - message: `"${key}" cannot be undefined, empty, or null. Please provide a valid value.`, - errorKind: LIT_ERROR.PARAM_NULL_ERROR.kind, - errorCode: LIT_ERROR.PARAM_NULL_ERROR.name, - }); - } - }); + // ========== Validate Params ========== + // -- validate: If it's NOT ready + if (!this.ready) { + const message = + '[signSessionKey] ]LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; - // -- validate present of accepted auth methods - if ( - !params.sessionSigs && - (!params.authMethods || params.authMethods.length <= 0) - ) { throwError({ - message: `Either sessionSigs or authMethods (length > 0) must be present.`, - errorKind: LIT_ERROR.PARAM_NULL_ERROR.kind, - errorCode: LIT_ERROR.PARAM_NULL_ERROR.name, + message, + errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, + errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, }); } - // ========== Get Node Promises ========== - // Handle promises for commands sent to Lit nodes - const wrapper = async ( - id: string - ): Promise | RejectedNodePromises> => { - const nodePromises = this.getNodePromises((url: string) => { - // -- get the session sig from the url key - const sessionSig = this.getSessionSigByUrl({ - sessionSigs: params.sessionSigs, - url, - }); + // -- construct SIWE message that will be signed by node to generate an authSig. + const _expiration = + params.expiration || + new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); - const reqBody: JsonPkpSignRequest = { - toSign: normalizeArray(params.toSign), - pubkey: hexPrefixed(params.pubKey), - authSig: sessionSig, + // Try to get it from local storage, if not generates one~ + const sessionKey: SessionKeyPair = + params.sessionKey ?? this.getSessionKey(); + const sessionKeyUri = LIT_SESSION_KEY_URI + sessionKey.publicKey; - // -- optional params - ...(params.authMethods && - params.authMethods.length > 0 && { - authMethods: params.authMethods, - }), - }; + log( + `[signSessionKey] sessionKeyUri is not found in params, generating a new one`, + sessionKeyUri + ); - logWithRequestId(id, 'reqBody:', reqBody); + if (!sessionKeyUri) { + throw new Error( + '[signSessionKey] sessionKeyUri is not defined. Please provide a sessionKeyUri or a sessionKey.' + ); + } - const urlWithPath = composeLitUrl({ + // Compute the address from the public key if it's provided. Otherwise, the node will compute it. + const pkpEthAddress = (function () { + // prefix '0x' if it's not already prefixed + params.pkpPublicKey = hexPrefixed(params.pkpPublicKey!); + + if (params.pkpPublicKey) return computeAddress(params.pkpPublicKey); + + // This will be populated by the node, using dummy value for now. + return '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; + })(); + + let siwe_statement = 'Lit Protocol PKP session signature'; + if (params.statement) { + siwe_statement += ' ' + params.statement; + log(`[signSessionKey] statement found in params: "${params.statement}"`); + } + + let siweMessage; + + const siweParams = { + domain: params?.domain || globalThis.location?.host || 'litprotocol.com', + walletAddress: pkpEthAddress, + statement: siwe_statement, + uri: sessionKeyUri, + version: '1', + chainId: params.chainId ?? 1, + expiration: _expiration, + nonce: this.latestBlockhash!, + }; + + if (params.resourceAbilityRequests) { + siweMessage = await createSiweMessageWithRecaps({ + ...siweParams, + resources: params.resourceAbilityRequests, + litNodeClient: this, + }); + } else { + siweMessage = await createSiweMessage(siweParams); + } + + // ========== Get Node Promises ========== + // -- fetch shares from nodes + const body: JsonSignSessionKeyRequestV1 = { + sessionKey: sessionKeyUri, + authMethods: params.authMethods, + ...(params?.pkpPublicKey && { pkpPublicKey: params.pkpPublicKey }), + siweMessage: siweMessage, + curveType: LIT_CURVE.BLS, + + // -- custom auths + ...(params?.litActionIpfsId && { + litActionIpfsId: params.litActionIpfsId, + }), + ...(params?.litActionCode && { code: params.litActionCode }), + ...(params?.jsParams && { jsParams: params.jsParams }), + ...(this.currentEpochNumber && { epoch: this.currentEpochNumber }), + }; + + log(`[signSessionKey] body:`, body); + + const wrapper = async ( + id: string + ): Promise | RejectedNodePromises> => { + logWithRequestId(id, 'signSessionKey body', body); + const nodePromises = this._getNodePromises((url: string) => { + const reqBody: JsonSignSessionKeyRequestV1 = body; + + const urlWithPath = composeLitUrl({ url, - endpoint: LIT_ENDPOINT.PKP_SIGN, + endpoint: LIT_ENDPOINT.SIGN_SESSION_KEY, }); - return this.generatePromise(urlWithPath, reqBody, id); + return this.#generatePromise(urlWithPath, reqBody, id); }); - const res = await this.handleNodePromises( - nodePromises, - id, - this.connectedNodes.size // ECDSA requires responses from all nodes, but only shares from minNodeCount. - ); + // -- resolve promises + let res; + try { + res = await this._handleNodePromises( + nodePromises, + id, + this.connectedNodes.size + ); + log('signSessionKey node promises:', res); + } catch (e) { + throw new Error(`Error when handling node promises: ${e}`); + } return res; - }; // wrapper end + }; - // ========== Execute with Retry ========== const res = await executeWithRetry< RejectedNodePromises | SuccessNodePromises >( wrapper, - (error: any, requestId: string, isFinal: boolean) => { + (_error: any, _requestId: string, isFinal: boolean) => { if (!isFinal) { - logError('errror occured, retrying operation'); + logError('an error occured, attempting to retry '); } }, this.config.retryTolerance ); - // ========== Handle Response ========== const requestId = res.requestId; + logWithRequestId(requestId, 'handleNodePromises res:', res); // -- case: promises rejected - if (!res.success) { + if (!this.#isSuccessNodePromises(res)) { this._throwNodeError(res as RejectedNodePromises, requestId); + return {} as SignSessionKeyResponse; } - // -- case: promises success (TODO: check the keys of "values") - const responseData = (res as SuccessNodePromises).values; - + const responseData: BlsResponseData[] = res.values; logWithRequestId( requestId, - 'responseData', + '[signSessionKey] responseData', JSON.stringify(responseData, null, 2) ); // ========== Extract shares from response data ========== // -- 1. combine signed data as a list, and get the signatures from it - const signedDataList = parsePkpSignResponse(responseData); + let curveType = responseData[0]?.curveType; - const signatures = getSignatures<{ signature: SigResponse }>({ + if (!curveType) { + log(`[signSessionKey] curveType not found. Defaulting to ECDSA.`); + curveType = 'ECDSA'; + } + + log(`[signSessionKey] curveType is "${curveType}"`); + + let signedDataList = responseData.map((s) => s.dataSigned); + + if (signedDataList.length <= 0) { + const err = `[signSessionKey] signedDataList is empty.`; + log(err); + throw new Error(err); + } + + logWithRequestId( requestId, - networkPubKeySet: this.networkPubKeySet, - minNodeCount: this.config.minNodeCount, - signedData: signedDataList, - }); + '[signSessionKey] signedDataList', + signedDataList + ); - logWithRequestId(requestId, `signature combination`, signatures); + // -- checking if we have enough shares + const validatedSignedDataList = responseData + .map((data: BlsResponseData) => { + // each of this field cannot be empty + let requiredFields = [ + 'signatureShare', + 'curveType', + 'shareIndex', + 'siweMessage', + 'dataSigned', + 'blsRootPubkey', + 'result', + ]; - return signatures.signature; // only a single signature is ever present, so we just return it. + // check if all required fields are present + for (const field of requiredFields) { + const key: keyof BlsResponseData = field as keyof BlsResponseData; + + if (!data[key] || data[key] === '') { + log( + `[signSessionKey] Invalid signed data. "${field}" is missing. Not a problem, we only need ${this.config.minNodeCount} nodes to sign the session key.` + ); + return null; + } + } + + if (!data.signatureShare.ProofOfPossession) { + const err = `[signSessionKey] Invalid signed data. "ProofOfPossession" is missing.`; + log(err); + throw new Error(err); + } + + return data; + }) + .filter((item) => item !== null); + + logWithRequestId( + requestId, + '[signSessionKey] requested length:', + signedDataList.length + ); + logWithRequestId( + requestId, + '[signSessionKey] validated length:', + validatedSignedDataList.length + ); + logWithRequestId( + requestId, + '[signSessionKey] minimum required length:', + this.config.minNodeCount + ); + if (validatedSignedDataList.length < this.config.minNodeCount) { + throw new Error( + `[signSessionKey] not enough nodes signed the session key. Expected ${this.config.minNodeCount}, got ${validatedSignedDataList.length}` + ); + } + + const blsSignedData: BlsResponseData[] = + validatedSignedDataList as BlsResponseData[]; + + const sigType = mostCommonString(blsSignedData.map((s) => s.curveType)); + log(`[signSessionKey] sigType:`, sigType); + + const signatureShares = getBlsSignatures(blsSignedData); + + log(`[signSessionKey] signatureShares:`, signatureShares); + + const blsCombinedSignature = blsSdk.combine_signature_shares( + signatureShares.map((s) => JSON.stringify(s)) + ); + + log(`[signSessionKey] blsCombinedSignature:`, blsCombinedSignature); + + const publicKey = removeHexPrefix(params.pkpPublicKey); + log(`[signSessionKey] publicKey:`, publicKey); + + const dataSigned = mostCommonString( + blsSignedData.map((s: any) => s.dataSigned) + ); + log(`[signSessionKey] dataSigned:`, dataSigned); + + const mostCommonSiweMessage = mostCommonString( + blsSignedData.map((s: any) => s.siweMessage) + ); + + log(`[signSessionKey] mostCommonSiweMessage:`, mostCommonSiweMessage); + + const signedMessage = normalizeAndStringify(mostCommonSiweMessage); + + log(`[signSessionKey] signedMessage:`, signedMessage); + + const signSessionKeyRes: SignSessionKeyResponse = { + authSig: { + sig: JSON.stringify({ + ProofOfPossession: blsCombinedSignature, + }), + algo: 'LIT_BLS', + derivedVia: 'lit.bls', + signedMessage, + address: computeAddress(hexPrefixed(publicKey)), + }, + pkpPublicKey: publicKey, + }; + + return signSessionKeyRes; }; /** * - * Request a signed JWT from the LIT network. Before calling this function, you must know the access control conditions for the item you wish to gain authorization for. - * - * @param { GetSignedTokenRequest } params + * Execute JS on the nodes and combine and return any resulting signatures + * Endpoint: /web/execute + * @param { JsonExecutionSdkParams } params * - * @returns { Promise } final JWT + * @returns { ExecuteJsResponse } * */ - getSignedToken = async (params: GetSignedTokenRequest): Promise => { - // ========== Prepare Params ========== - const { chain, authSig, sessionSigs } = params; - - // ========== Validation ========== - // -- validate if it's ready + executeJs = async ( + params: JsonExecutionSdkParams + ): Promise => { + // ========== Validate Params ========== if (!this.ready) { const message = - '3 LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; + '[executeJs] LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; + throwError({ message, errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, @@ -1305,450 +1547,307 @@ export class LitNodeClientNodeJs }); } - // -- validate if this.networkPubKeySet is null - if (this.networkPubKeySet === null) { - return throwError({ - message: 'networkPubKeySet cannot be null', - errorKind: LIT_ERROR.PARAM_NULL_ERROR.kind, - errorCode: LIT_ERROR.PARAM_NULL_ERROR.name, - }); - } - const paramsIsSafe = safeParams({ - functionName: 'getSignedToken', - params, + functionName: 'executeJs', + params: params, }); if (!paramsIsSafe) { return throwError({ - message: `Parameter validation failed.`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + message: 'executeJs params are not valid', + errorKind: LIT_ERROR.INVALID_PARAM_TYPE.kind, + errorCode: LIT_ERROR.INVALID_PARAM_TYPE.name, }); } - // ========== Prepare ========== - // we need to send jwt params iat (issued at) and exp (expiration) - // because the nodes may have different wall clock times - // the nodes will verify that these params are withing a grace period - const { iat, exp } = this.getJWTParams(); - - // ========== Formatting Access Control Conditions ========= - const { - error, - formattedAccessControlConditions, - formattedEVMContractConditions, - formattedSolRpcConditions, - formattedUnifiedAccessControlConditions, - }: FormattedMultipleAccs = this.getFormattedAccessControlConditions(params); - - if (error) { - return throwError({ - message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, - }); - } + // Format the params + const formattedParams: JsonExecutionSdkParams = { + ...params, + ...(params.jsParams && { jsParams: normalizeJsParams(params.jsParams) }), + ...(params.code && { code: encodeCode(params.code) }), + }; // ========== Get Node Promises ========== + // Handle promises for commands sent to Lit nodes const wrapper = async ( - id: string + requestId: string ): Promise | RejectedNodePromises> => { - const nodePromises = this.getNodePromises((url: string) => { - // -- if session key is available, use it - const authSigToSend = sessionSigs ? sessionSigs[url] : authSig; + const nodePromises = this._getNodePromises(async (url: string) => { + // -- choose the right signature + const sessionSig = this._getSessionSigByUrl({ + sessionSigs: formattedParams.sessionSigs, + url, + }); - const reqBody: SigningAccessControlConditionRequest = { - accessControlConditions: formattedAccessControlConditions, - evmContractConditions: formattedEVMContractConditions, - solRpcConditions: formattedSolRpcConditions, - unifiedAccessControlConditions: - formattedUnifiedAccessControlConditions, - chain, - authSig: authSigToSend, - iat, - exp, + const reqBody: JsonExecutionRequest = { + ...formattedParams, + authSig: sessionSig, }; const urlWithPath = composeLitUrl({ url, - endpoint: LIT_ENDPOINT.SIGN_ACCS, + endpoint: LIT_ENDPOINT.EXECUTE_JS, }); - return this.generatePromise(urlWithPath, reqBody, id); + return this.#generatePromise(urlWithPath, reqBody, requestId); }); // -- resolve promises - const res = await this.handleNodePromises( + const res = await this._handleNodePromises( nodePromises, - id, - this.config.minNodeCount + requestId, + this.connectedNodes.size ); + return res; - }; + }; // wrapper end + // ========== Execute with Retry ========== const res = await executeWithRetry< RejectedNodePromises | SuccessNodePromises >( wrapper, (error: any, requestId: string, isFinal: boolean) => { - if (!isFinal) { - logError('an error occured, attempting to retry '); - } + logError('an error occured, attempting to retry operation'); }, this.config.retryTolerance ); + + // ========== Handle Response ========== const requestId = res.requestId; // -- case: promises rejected - if (res.success === false) { + if (!res.success) { this._throwNodeError(res as RejectedNodePromises, requestId); } - const signatureShares: NodeBlsSigningShare[] = ( - res as SuccessNodePromises - ).values; + // -- case: promises success (TODO: check the keys of "values") + const responseData = (res as SuccessNodePromises).values; - log('signatureShares', signatureShares); + logWithRequestId( + requestId, + 'executeJs responseData from node : ', + JSON.stringify(responseData, null, 2) + ); - // ========== Result ========== - const finalJwt: string = this.combineSharesAndGetJWT( - signatureShares, - requestId + // -- find the responseData that has the most common response + const mostCommonResponse = findMostCommonResponse( + responseData + ) as NodeShare; + + const responseFromStrategy: any = processLitActionResponseStrategy( + responseData, + params.responseStrategy ?? { strategy: 'leastCommon' } ); + mostCommonResponse.response = responseFromStrategy; - return finalJwt; - }; + const isSuccess = mostCommonResponse.success; + const hasSignedData = Object.keys(mostCommonResponse.signedData).length > 0; + const hasClaimData = Object.keys(mostCommonResponse.claimData).length > 0; - /** - * - * Encrypt data using the LIT network public key. - * - * @param { EncryptSdkParams } params - * @param params.dataToEncrypt - The data to encrypt - * @param params.accessControlConditions - (optional) The access control conditions for the data - * @param params.evmContractConditions - (optional) The EVM contract conditions for the data - * @param params.solRpcConditions - (optional) The Solidity RPC conditions for the data - * @param params.unifiedAccessControlConditions - (optional) The unified access control conditions for the data - * - * @return { Promise } The encrypted ciphertext and the hash of the data - * - * @throws { Error } if the LIT node client is not ready - * @throws { Error } if the subnetPubKey is null - */ - encrypt = async (params: EncryptSdkParams): Promise => { - // ========== Validate Params ========== - // -- validate if it's ready - if (!this.ready) { - const message = - '6 LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; - throwError({ - message, - errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, - errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, - }); + // -- we must also check for claim responses as a user may have submitted for a claim and signatures must be aggregated before returning + if (isSuccess && !hasSignedData && !hasClaimData) { + return mostCommonResponse as unknown as ExecuteJsResponse; } - // -- validate if this.subnetPubKey is null - if (!this.subnetPubKey) { - const message = 'subnetPubKey cannot be null'; - return throwError({ - message, - errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, - errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, - }); + // -- in the case where we are not signing anything on Lit action and using it as purely serverless function + if (!hasSignedData && !hasClaimData) { + return { + claims: {}, + signatures: null, + decryptions: [], + response: mostCommonResponse.response, + logs: mostCommonResponse.logs, + } as ExecuteJsNoSigningResponse; } - const paramsIsSafe = safeParams({ - functionName: 'encrypt', - params, - }); + // ========== Extract shares from response data ========== - if (!paramsIsSafe) { - return throwError({ - message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, - }); - } + // -- 1. combine signed data as a list, and get the signatures from it + const signedDataList = responseData.map((r) => { + return removeDoubleQuotes(r.signedData); + }); - // ========== Validate Access Control Conditions Schema ========== - await this.validateAccessControlConditionsSchema(params); + logWithRequestId( + requestId, + 'signatures shares to combine: ', + signedDataList + ); - // ========== Hashing Access Control Conditions ========= - // hash the access control conditions - const hashOfConditions: ArrayBuffer | undefined = - await this.getHashedAccessControlConditions(params); + const signatures = getSignatures({ + requestId, + networkPubKeySet: this.networkPubKeySet, + minNodeCount: this.config.minNodeCount, + signedData: signedDataList, + }); - if (!hashOfConditions) { - return throwError({ - message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, - }); - } + // -- 2. combine responses as a string, and parse it as JSON if possible + const parsedResponse = parseAsJsonOrString(mostCommonResponse.response); - const hashOfConditionsStr = uint8arrayToString( - new Uint8Array(hashOfConditions), - 'base16' + // -- 3. combine logs + const mostCommonLogs: string = mostCommonString( + responseData.map((r: NodeLog) => r.logs) ); - // ========== Hashing Private Data ========== - // hash the private data - const hashOfPrivateData = await crypto.subtle.digest( - 'SHA-256', - params.dataToEncrypt - ); - const hashOfPrivateDataStr = uint8arrayToString( - new Uint8Array(hashOfPrivateData), - 'base16' - ); + // -- 4. combine claims + const claimsList = getClaimsList(responseData); + const claims = claimsList.length > 0 ? getClaims(claimsList) : undefined; - // ========== Assemble identity parameter ========== - const identityParam = this.#getIdentityParamForEncryption( - hashOfConditionsStr, - hashOfPrivateDataStr - ); + // ========== Result ========== + const returnVal: ExecuteJsResponse = { + claims, + signatures, + // decryptions: [], + response: parsedResponse, + logs: mostCommonLogs, + }; - // ========== Encrypt ========== - const ciphertext = encrypt( - this.subnetPubKey, - params.dataToEncrypt, - uint8arrayFromString(identityParam, 'utf8') - ); + log('returnVal:', returnVal); - return { ciphertext, dataToEncryptHash: hashOfPrivateDataStr }; + return returnVal; }; /** + * Use PKP to sign * - * Decrypt ciphertext with the LIT network. + * Endpoint: /web/pkp/sign * + * @param { JsonPkpSignSdkParams } params + * @param params.toSign - The data to sign + * @param params.pubKey - The public key to sign with + * @param params.sessionSigs - The session signatures to use + * @param params.authMethods - (optional) The auth methods to use */ - decrypt = async (params: DecryptRequest): Promise => { - const { sessionSigs, chain, ciphertext, dataToEncryptHash } = params; + pkpSign = async (params: JsonPkpSignSdkParams): Promise => { + // -- validate required params + const requiredParamKeys = ['toSign', 'pubKey']; - // ========== Validate Params ========== - // -- validate if it's ready - if (!this.ready) { - const message = - '6 LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; - throwError({ - message, - errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, - errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, - }); - } - - // -- validate if this.subnetPubKey is null - if (!this.subnetPubKey) { - const message = 'subnetPubKey cannot be null'; - return throwError({ - message, - errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, - errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, - }); - } - - const paramsIsSafe = safeParams({ - functionName: 'decrypt', - params, + (requiredParamKeys as (keyof JsonPkpSignSdkParams)[]).forEach((key) => { + if (!params[key]) { + throwError({ + message: `"${key}" cannot be undefined, empty, or null. Please provide a valid value.`, + errorKind: LIT_ERROR.PARAM_NULL_ERROR.kind, + errorCode: LIT_ERROR.PARAM_NULL_ERROR.name, + }); + } }); - if (!paramsIsSafe) { - return throwError({ - message: `Parameter validation failed.`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, - }); - } - - // ========== Hashing Access Control Conditions ========= - // hash the access control conditions - const hashOfConditions: ArrayBuffer | undefined = - await this.getHashedAccessControlConditions(params); - - if (!hashOfConditions) { - return throwError({ - message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, - }); - } - - const hashOfConditionsStr = uint8arrayToString( - new Uint8Array(hashOfConditions), - 'base16' - ); - - // ========== Formatting Access Control Conditions ========= - const { - error, - formattedAccessControlConditions, - formattedEVMContractConditions, - formattedSolRpcConditions, - formattedUnifiedAccessControlConditions, - }: FormattedMultipleAccs = this.getFormattedAccessControlConditions(params); - - if (error) { + // -- validate present of accepted auth methods + if ( + !params.sessionSigs && + (!params.authMethods || params.authMethods.length <= 0) + ) { throwError({ - message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + message: `Either sessionSigs or authMethods (length > 0) must be present.`, + errorKind: LIT_ERROR.PARAM_NULL_ERROR.kind, + errorCode: LIT_ERROR.PARAM_NULL_ERROR.name, }); } - // ========== Assemble identity parameter ========== - const identityParam = this.#getIdentityParamForEncryption( - hashOfConditionsStr, - dataToEncryptHash - ); - - log('identityParam', identityParam); - - // ========== Get Network Signature ========== + // ========== Get Node Promises ========== + // Handle promises for commands sent to Lit nodes const wrapper = async ( id: string ): Promise | RejectedNodePromises> => { - const nodePromises = this.getNodePromises((url: string) => { - // -- if session key is available, use it - const authSigToSend = sessionSigs ? sessionSigs[url] : params.authSig; + const nodePromises = this._getNodePromises((url: string) => { + // -- get the session sig from the url key + const sessionSig = this._getSessionSigByUrl({ + sessionSigs: params.sessionSigs, + url, + }); - if (!authSigToSend) { - return throwError({ - message: `authSig is required`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, - }); - } + const reqBody: JsonPkpSignRequest = { + toSign: normalizeArray(params.toSign), + pubkey: hexPrefixed(params.pubKey), + authSig: sessionSig, - const reqBody: EncryptionSignRequest = { - accessControlConditions: formattedAccessControlConditions, - evmContractConditions: formattedEVMContractConditions, - solRpcConditions: formattedSolRpcConditions, - unifiedAccessControlConditions: - formattedUnifiedAccessControlConditions, - dataToEncryptHash, - chain, - authSig: authSigToSend, - epoch: this.currentEpochNumber!, + // -- optional params + ...(params.authMethods && + params.authMethods.length > 0 && { + authMethods: params.authMethods, + }), }; - const urlWithParh = composeLitUrl({ + logWithRequestId(id, 'reqBody:', reqBody); + + const urlWithPath = composeLitUrl({ url, - endpoint: LIT_ENDPOINT.ENCRYPTION_SIGN, + endpoint: LIT_ENDPOINT.PKP_SIGN, }); - return this.generatePromise(urlWithParh, reqBody, id); + return this.#generatePromise(urlWithPath, reqBody, id); }); - // -- resolve promises - const res = await this.handleNodePromises( + const res = await this._handleNodePromises( nodePromises, id, - this.config.minNodeCount + this.connectedNodes.size // ECDSA requires responses from all nodes, but only shares from minNodeCount. ); return res; - }; + }; // wrapper end + // ========== Execute with Retry ========== const res = await executeWithRetry< RejectedNodePromises | SuccessNodePromises >( wrapper, - (_error: string, _requestId: string, _isFinal: boolean) => { - logError('an error occured attempting to retry'); + (error: any, requestId: string, isFinal: boolean) => { + if (!isFinal) { + logError('errror occured, retrying operation'); + } }, this.config.retryTolerance ); + // ========== Handle Response ========== const requestId = res.requestId; // -- case: promises rejected - if (res.success === false) { + if (!res.success) { this._throwNodeError(res as RejectedNodePromises, requestId); } - const signatureShares: NodeBlsSigningShare[] = ( - res as SuccessNodePromises - ).values; - - logWithRequestId(requestId, 'signatureShares', signatureShares); + // -- case: promises success (TODO: check the keys of "values") + const responseData = (res as SuccessNodePromises).values; - // ========== Result ========== - const decryptedData = this.#decryptWithSignatureShares( - this.subnetPubKey, - uint8arrayFromString(identityParam, 'utf8'), - ciphertext, - signatureShares + logWithRequestId( + requestId, + 'responseData', + JSON.stringify(responseData, null, 2) ); - return { decryptedData }; - }; - - getLitResourceForEncryption = async ( - params: EncryptRequest - ): Promise => { - // ========== Hashing Access Control Conditions ========= - // hash the access control conditions - const hashOfConditions: ArrayBuffer | undefined = - await this.getHashedAccessControlConditions(params); - - if (!hashOfConditions) { - return throwError({ - message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, - errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, - errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, - }); - } - - const hashOfConditionsStr = uint8arrayToString( - new Uint8Array(hashOfConditions), - 'base16' - ); + // ========== Extract shares from response data ========== + // -- 1. combine signed data as a list, and get the signatures from it + const signedDataList = parsePkpSignResponse(responseData); - // ========== Hashing Private Data ========== - // hash the private data - const hashOfPrivateData = await crypto.subtle.digest( - 'SHA-256', - params.dataToEncrypt - ); - const hashOfPrivateDataStr = uint8arrayToString( - new Uint8Array(hashOfPrivateData), - 'base16' - ); + const signatures = getSignatures<{ signature: SigResponse }>({ + requestId, + networkPubKeySet: this.networkPubKeySet, + minNodeCount: this.config.minNodeCount, + signedData: signedDataList, + }); - return new LitAccessControlConditionResource( - `${hashOfConditionsStr}/${hashOfPrivateDataStr}` - ); - }; + logWithRequestId(requestId, `signature combination`, signatures); - #getIdentityParamForEncryption = ( - hashOfConditionsStr: string, - hashOfPrivateDataStr: string - ): string => { - return new LitAccessControlConditionResource( - `${hashOfConditionsStr}/${hashOfPrivateDataStr}` - ).getResourceKey(); + return signatures.signature; // only a single signature is ever present, so we just return it. }; - /** ============================== SESSION ============================== */ - /** - * Sign a session public key using a PKP, which generates an authSig. - * @returns {Object} An object containing the resulting signature. + * Authenticates an Auth Method for claiming a Programmable Key Pair (PKP). + * A {@link MintCallback} can be defined for custom on chain interactions + * by default the callback will forward to a relay server for minting on chain. + * + * Endpoint: /web/pkp/claim + * + * @param {ClaimKeyRequest} params an Auth Method and {@link MintCallback} + * @returns {Promise} */ - - signSessionKey = async ( - params: SignSessionKeyProp - ): Promise => { - log(`[signSessionKey] params:`, params); - - // ========== Validate Params ========== - // -- validate: If it's NOT ready + async claimKeyId( + params: ClaimRequest + ): Promise { if (!this.ready) { const message = - '[signSessionKey] ]LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; - + 'LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; throwError({ message, errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, @@ -1756,114 +1855,233 @@ export class LitNodeClientNodeJs }); } - // -- construct SIWE message that will be signed by node to generate an authSig. - const _expiration = - params.expiration || - new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); + if (params.authMethod.authMethodType == AuthMethodType.WebAuthn) { + throwError({ + message: + 'Unsupported auth method type. Webauthn, and Lit Actions are not supported for claiming', + errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, + errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, + }); + } + let requestId; + const wrapper = async ( + id: string + ): Promise | RejectedNodePromises> => { + const nodePromises = this._getNodePromises((url: string) => { + if (!params.authMethod) { + throw new Error('authMethod is required'); + } - // Try to get it from local storage, if not generates one~ - const sessionKey: SessionKeyPair = - params.sessionKey ?? this.getSessionKey(); - const sessionKeyUri = LIT_SESSION_KEY_URI + sessionKey.publicKey; + const reqBody: JsonPKPClaimKeyRequest = { + authMethod: params.authMethod, + }; - log( - `[signSessionKey] sessionKeyUri is not found in params, generating a new one`, - sessionKeyUri + const urlWithPath = composeLitUrl({ + url, + endpoint: LIT_ENDPOINT.PKP_CLAIM, + }); + + return this.#generatePromise(urlWithPath, reqBody, id); + }); + + const responseData = await this._handleNodePromises( + nodePromises, + id, + this.connectedNodes.size + ); + + return responseData; + }; + + const responseData = await executeWithRetry< + RejectedNodePromises | SuccessNodePromises + >( + wrapper, + (_error: any, _requestId: string, isFinal: boolean) => { + if (!isFinal) { + logError('an error occured, attempting to retry'); + } + }, + this.config.retryTolerance ); + requestId = responseData.requestId; - if (!sessionKeyUri) { - throw new Error( - '[signSessionKey] sessionKeyUri is not defined. Please provide a sessionKeyUri or a sessionKey.' + if (responseData.success === true) { + const nodeSignatures: Signature[] = ( + responseData as SuccessNodePromises + ).values.map((r: any) => { + const sig = ethers.utils.splitSignature(`0x${r.signature}`); + return { + r: sig.r, + s: sig.s, + v: sig.v, + }; + }); + + logWithRequestId( + requestId, + `responseData: ${JSON.stringify(responseData, null, 2)}` ); - } - // Compute the address from the public key if it's provided. Otherwise, the node will compute it. - const pkpEthAddress = (function () { - // prefix '0x' if it's not already prefixed - params.pkpPublicKey = hexPrefixed(params.pkpPublicKey!); + const derivedKeyId = (responseData as SuccessNodePromises).values[0] + .derivedKeyId; - if (params.pkpPublicKey) return computeAddress(params.pkpPublicKey); + const pubkey: string = this.computeHDPubKey(derivedKeyId); + logWithRequestId( + requestId, + `pubkey ${pubkey} derived from key id ${derivedKeyId}` + ); - // This will be populated by the node, using dummy value for now. - return '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; - })(); + const relayParams: ClaimRequest<'relay'> = + params as ClaimRequest<'relay'>; - let siwe_statement = 'Lit Protocol PKP session signature'; - if (params.statement) { - siwe_statement += ' ' + params.statement; - log(`[signSessionKey] statement found in params: "${params.statement}"`); + let mintTx = ''; + if (params.mintCallback && 'signer' in params) { + mintTx = await params.mintCallback( + { + derivedKeyId, + authMethodType: params.authMethod.authMethodType, + signatures: nodeSignatures, + pubkey, + signer: (params as ClaimRequest<'client'>).signer, + ...relayParams, + }, + this.config.litNetwork as LitNetwork + ); + } else { + mintTx = await defaultMintClaimCallback( + { + derivedKeyId, + authMethodType: params.authMethod.authMethodType, + signatures: nodeSignatures, + pubkey, + ...relayParams, + }, + this.config.litNetwork as LitNetwork + ); + } + + return { + signatures: nodeSignatures, + claimedKeyId: derivedKeyId, + pubkey, + mintTx, + }; + } else { + return throwError({ + message: `Claim request has failed. Request trace id: lit_${requestId} `, + errorKind: LIT_ERROR.UNKNOWN_ERROR.kind, + errorCode: LIT_ERROR.UNKNOWN_ERROR.code, + }); } + } - let siweMessage; + /** + * + * Request a signed JWT from the LIT network. Before calling this function, you must know the access control conditions for the item you wish to gain authorization for. + * + * Endpoint: /web/signing/access_control_condition + * + * @param { GetSignedTokenRequest } params + * + * @returns { Promise } final JWT + * + */ + getSignedToken = async (params: GetSignedTokenRequest): Promise => { + // ========== Prepare Params ========== + const { chain, authSig, sessionSigs } = params; - const siweParams = { - domain: params?.domain || globalThis.location?.host || 'litprotocol.com', - walletAddress: pkpEthAddress, - statement: siwe_statement, - uri: sessionKeyUri, - version: '1', - chainId: params.chainId ?? 1, - expiration: _expiration, - nonce: this.latestBlockhash!, - }; + // ========== Validation ========== + // -- validate if it's ready + if (!this.ready) { + const message = + '3 LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; + throwError({ + message, + errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, + errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, + }); + } - if (params.resourceAbilityRequests) { - siweMessage = await createSiweMessageWithRecaps({ - ...siweParams, - resources: params.resourceAbilityRequests, - litNodeClient: this, + // -- validate if this.networkPubKeySet is null + if (this.networkPubKeySet === null) { + return throwError({ + message: 'networkPubKeySet cannot be null', + errorKind: LIT_ERROR.PARAM_NULL_ERROR.kind, + errorCode: LIT_ERROR.PARAM_NULL_ERROR.name, }); - } else { - siweMessage = await createSiweMessage(siweParams); } - // ========== Get Node Promises ========== - // -- fetch shares from nodes - const body: JsonSignSessionKeyRequestV1 = { - sessionKey: sessionKeyUri, - authMethods: params.authMethods, - ...(params?.pkpPublicKey && { pkpPublicKey: params.pkpPublicKey }), - siweMessage: siweMessage, - curveType: LIT_CURVE.BLS, + const paramsIsSafe = safeParams({ + functionName: 'getSignedToken', + params, + }); - // -- custom auths - ...(params?.litActionIpfsId && { - litActionIpfsId: params.litActionIpfsId, - }), - ...(params?.litActionCode && { code: params.litActionCode }), - ...(params?.jsParams && { jsParams: params.jsParams }), - ...(this.currentEpochNumber && { epoch: this.currentEpochNumber }), - }; + if (!paramsIsSafe) { + return throwError({ + message: `Parameter validation failed.`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + }); + } - log(`[signSessionKey] body:`, body); + // ========== Prepare ========== + // we need to send jwt params iat (issued at) and exp (expiration) + // because the nodes may have different wall clock times + // the nodes will verify that these params are withing a grace period + const { iat, exp } = this.#getJWTParams(); + + // ========== Formatting Access Control Conditions ========= + const { + error, + formattedAccessControlConditions, + formattedEVMContractConditions, + formattedSolRpcConditions, + formattedUnifiedAccessControlConditions, + }: FormattedMultipleAccs = this.getFormattedAccessControlConditions(params); + + if (error) { + return throwError({ + message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + }); + } + // ========== Get Node Promises ========== const wrapper = async ( id: string ): Promise | RejectedNodePromises> => { - logWithRequestId(id, 'signSessionKey body', body); - const nodePromises = this.getNodePromises((url: string) => { - const reqBody: JsonSignSessionKeyRequestV1 = body; + const nodePromises = this._getNodePromises((url: string) => { + // -- if session key is available, use it + const authSigToSend = sessionSigs ? sessionSigs[url] : authSig; + + const reqBody: SigningAccessControlConditionRequest = { + accessControlConditions: formattedAccessControlConditions, + evmContractConditions: formattedEVMContractConditions, + solRpcConditions: formattedSolRpcConditions, + unifiedAccessControlConditions: + formattedUnifiedAccessControlConditions, + chain, + authSig: authSigToSend, + iat, + exp, + }; const urlWithPath = composeLitUrl({ url, - endpoint: LIT_ENDPOINT.SIGN_SESSION_KEY, + endpoint: LIT_ENDPOINT.SIGN_ACCS, }); - return this.generatePromise(urlWithPath, reqBody, id); + return this.#generatePromise(urlWithPath, reqBody, id); }); // -- resolve promises - let res; - try { - res = await this.handleNodePromises( - nodePromises, - id, - this.connectedNodes.size - ); - log('signSessionKey node promises:', res); - } catch (e) { - throw new Error(`Error when handling node promises: ${e}`); - } + const res = await this._handleNodePromises( + nodePromises, + id, + this.config.minNodeCount + ); return res; }; @@ -1871,485 +2089,50 @@ export class LitNodeClientNodeJs RejectedNodePromises | SuccessNodePromises >( wrapper, - (_error: any, _requestId: string, isFinal: boolean) => { + (error: any, requestId: string, isFinal: boolean) => { if (!isFinal) { - logError('an error occured, attempting to retry '); - } - }, - this.config.retryTolerance - ); - - const requestId = res.requestId; - logWithRequestId(requestId, 'handleNodePromises res:', res); - - // -- case: promises rejected - if (!this.#isSuccessNodePromises(res)) { - this._throwNodeError(res as RejectedNodePromises, requestId); - return {} as SignSessionKeyResponse; - } - - const responseData: BlsResponseData[] = res.values; - logWithRequestId( - requestId, - '[signSessionKey] responseData', - JSON.stringify(responseData, null, 2) - ); - - // ========== Extract shares from response data ========== - // -- 1. combine signed data as a list, and get the signatures from it - let curveType = responseData[0]?.curveType; - - if (!curveType) { - log(`[signSessionKey] curveType not found. Defaulting to ECDSA.`); - curveType = 'ECDSA'; - } - - log(`[signSessionKey] curveType is "${curveType}"`); - - let signedDataList = responseData.map((s) => s.dataSigned); - - if (signedDataList.length <= 0) { - const err = `[signSessionKey] signedDataList is empty.`; - log(err); - throw new Error(err); - } - - logWithRequestId( - requestId, - '[signSessionKey] signedDataList', - signedDataList - ); - - // -- checking if we have enough shares - const validatedSignedDataList = responseData - .map((data: BlsResponseData) => { - // each of this field cannot be empty - let requiredFields = [ - 'signatureShare', - 'curveType', - 'shareIndex', - 'siweMessage', - 'dataSigned', - 'blsRootPubkey', - 'result', - ]; - - // check if all required fields are present - for (const field of requiredFields) { - const key: keyof BlsResponseData = field as keyof BlsResponseData; - - if (!data[key] || data[key] === '') { - log( - `[signSessionKey] Invalid signed data. "${field}" is missing. Not a problem, we only need ${this.config.minNodeCount} nodes to sign the session key.` - ); - return null; - } - } - - if (!data.signatureShare.ProofOfPossession) { - const err = `[signSessionKey] Invalid signed data. "ProofOfPossession" is missing.`; - log(err); - throw new Error(err); - } - - return data; - }) - .filter((item) => item !== null); - - logWithRequestId( - requestId, - '[signSessionKey] requested length:', - signedDataList.length - ); - logWithRequestId( - requestId, - '[signSessionKey] validated length:', - validatedSignedDataList.length - ); - logWithRequestId( - requestId, - '[signSessionKey] minimum required length:', - this.config.minNodeCount - ); - if (validatedSignedDataList.length < this.config.minNodeCount) { - throw new Error( - `[signSessionKey] not enough nodes signed the session key. Expected ${this.config.minNodeCount}, got ${validatedSignedDataList.length}` - ); - } - - const blsSignedData: BlsResponseData[] = - validatedSignedDataList as BlsResponseData[]; - - const sigType = mostCommonString(blsSignedData.map((s) => s.curveType)); - log(`[signSessionKey] sigType:`, sigType); - - const signatureShares = getBlsSignatures(blsSignedData); - - log(`[signSessionKey] signatureShares:`, signatureShares); - - const blsCombinedSignature = blsSdk.combine_signature_shares( - signatureShares.map((s) => JSON.stringify(s)) - ); - - log(`[signSessionKey] blsCombinedSignature:`, blsCombinedSignature); - - const publicKey = removeHexPrefix(params.pkpPublicKey); - log(`[signSessionKey] publicKey:`, publicKey); - - const dataSigned = mostCommonString( - blsSignedData.map((s: any) => s.dataSigned) - ); - log(`[signSessionKey] dataSigned:`, dataSigned); - - const mostCommonSiweMessage = mostCommonString( - blsSignedData.map((s: any) => s.siweMessage) - ); - - log(`[signSessionKey] mostCommonSiweMessage:`, mostCommonSiweMessage); - - const signedMessage = normalizeAndStringify(mostCommonSiweMessage); - - log(`[signSessionKey] signedMessage:`, signedMessage); - - const signSessionKeyRes: SignSessionKeyResponse = { - authSig: { - sig: JSON.stringify({ - ProofOfPossession: blsCombinedSignature, - }), - algo: 'LIT_BLS', - derivedVia: 'lit.bls', - signedMessage, - address: computeAddress(hexPrefixed(publicKey)), - }, - pkpPublicKey: publicKey, - }; - - return signSessionKeyRes; - }; - - #isSuccessNodePromises = (res: any): res is SuccessNodePromises => { - return res.success === true; - }; - - getSignSessionKeyShares = async ( - url: string, - params: GetSignSessionKeySharesProp, - requestId: string - ) => { - log('getSignSessionKeyShares'); - const urlWithPath = composeLitUrl({ - url, - endpoint: LIT_ENDPOINT.SIGN_SESSION_KEY, - }); - return await this.sendCommandToNode({ - url: urlWithPath, - data: params.body, - requestId, - }); - }; - - /** - * Get session signatures for a set of resources - * - * High level, how this works: - * 1. Generate or retrieve session key - * 2. Generate or retrieve the wallet signature of the session key - * 3. Sign the specific resources with the session key - * - * Note: When generating session signatures for different PKPs or auth methods, - * be sure to call disconnectWeb3 to clear auth signatures stored in local storage - * - * @param { GetSessionSigsProps } params - * - * @example - * - * ```ts - * import { LitPKPResource, LitActionResource } from "@lit-protocol/auth-helpers"; -import { LitAbility } from "@lit-protocol/types"; -import { logWithRequestId } from '../../../misc/src/lib/misc'; - -const resourceAbilityRequests = [ - { - resource: new LitPKPResource("*"), - ability: LitAbility.PKPSigning, - }, - { - resource: new LitActionResource("*"), - ability: LitAbility.LitActionExecution, - }, - ]; - * ``` - */ - getSessionSigs = async ( - params: GetSessionSigsProps - ): Promise => { - // -- prepare - // Try to get it from local storage, if not generates one~ - const sessionKey = params.sessionKey ?? this.getSessionKey(); - - const sessionKeyUri = this.getSessionKeyUri(sessionKey.publicKey); - - // First get or generate the session capability object for the specified resources. - const sessionCapabilityObject = params.sessionCapabilityObject - ? params.sessionCapabilityObject - : await this.generateSessionCapabilityObjectWithWildcards( - params.resourceAbilityRequests.map((r) => r.resource) - ); - const expiration = params.expiration || LitNodeClientNodeJs.getExpiration(); - - if (!this.latestBlockhash) { - throwError({ - message: 'Eth Blockhash is undefined.', - errorKind: LIT_ERROR.INVALID_ETH_BLOCKHASH.kind, - errorCode: LIT_ERROR.INVALID_ETH_BLOCKHASH.name, - }); - } - const nonce = this.latestBlockhash!; - - // -- (TRY) to get the wallet signature - let authSig = await this.getWalletSig({ - authNeededCallback: params.authNeededCallback, - chain: params.chain || 'ethereum', - sessionCapabilityObject, - switchChain: params.switchChain, - expiration: expiration, - sessionKey: sessionKey, - sessionKeyUri: sessionKeyUri, - nonce, - - // -- for recap - resourceAbilityRequests: params.resourceAbilityRequests, - - // -- optional fields - ...(params.litActionCode && { litActionCode: params.litActionCode }), - ...(params.litActionIpfsId && { - litActionIpfsId: params.litActionIpfsId, - }), - ...(params.jsParams && { jsParams: params.jsParams }), - }); - - const needToResignSessionKey = await this.checkNeedToResignSessionKey({ - authSig, - sessionKeyUri, - resourceAbilityRequests: params.resourceAbilityRequests, - }); - - // console.log('XXX needToResignSessionKey:', needToResignSessionKey); - - // -- (CHECK) if we need to resign the session key - if (needToResignSessionKey) { - log('need to re-sign session key. Signing...'); - authSig = await this.#authCallbackAndUpdateStorageItem({ - authCallback: params.authNeededCallback, - authCallbackParams: { - chain: params.chain || 'ethereum', - statement: sessionCapabilityObject.statement, - resources: [sessionCapabilityObject.encodeAsSiweResource()], - switchChain: params.switchChain, - expiration, - sessionKey: sessionKey, - uri: sessionKeyUri, - nonce, - resourceAbilityRequests: params.resourceAbilityRequests, - - // -- optional fields - ...(params.litActionCode && { litActionCode: params.litActionCode }), - ...(params.litActionIpfsId && { - litActionIpfsId: params.litActionIpfsId, - }), - ...(params.jsParams && { jsParams: params.jsParams }), - }, - }); - } - - if ( - authSig.address === '' || - authSig.derivedVia === '' || - authSig.sig === '' || - authSig.signedMessage === '' - ) { - throwError({ - message: 'No wallet signature found', - errorKind: LIT_ERROR.WALLET_SIGNATURE_NOT_FOUND_ERROR.kind, - errorCode: LIT_ERROR.WALLET_SIGNATURE_NOT_FOUND_ERROR.name, - }); - // @ts-ignore - we throw an error above, so below should never be reached - return; - } - - // ===== AFTER we have Valid Signed Session Key ===== - // - Let's sign the resources with the session key - // - 5 minutes is the default expiration for a session signature - // - Because we can generate a new session sig every time the user wants to access a resource without prompting them to sign with their wallet - const sessionExpiration = - expiration ?? new Date(Date.now() + 1000 * 60 * 5).toISOString(); - - const capabilities = params.capacityDelegationAuthSig - ? [ - ...(params.capabilityAuthSigs ?? []), - params.capacityDelegationAuthSig, - authSig, - ] - : [...(params.capabilityAuthSigs ?? []), authSig]; - - const signingTemplate = { - sessionKey: sessionKey.publicKey, - resourceAbilityRequests: params.resourceAbilityRequests, - capabilities, - issuedAt: new Date().toISOString(), - expiration: sessionExpiration, - }; - - const signatures: SessionSigsMap = {}; - - this.connectedNodes.forEach((nodeAddress: string) => { - const toSign: SessionSigningTemplate = { - ...signingTemplate, - nodeAddress, - }; - - const signedMessage = JSON.stringify(toSign); - - const uint8arrayKey = uint8arrayFromString( - sessionKey.secretKey, - 'base16' - ); - - const uint8arrayMessage = uint8arrayFromString(signedMessage, 'utf8'); - const signature = nacl.sign.detached(uint8arrayMessage, uint8arrayKey); - - signatures[nodeAddress] = { - sig: uint8arrayToString(signature, 'base16'), - derivedVia: 'litSessionSignViaNacl', - signedMessage: signedMessage, - address: sessionKey.publicKey, - algo: 'ed25519', - }; - }); - - log('signatures:', signatures); - - return signatures; - }; - - /** - * Retrieves the PKP sessionSigs. - * - * @param params - The parameters for retrieving the PKP sessionSigs. - * @returns A promise that resolves to the PKP sessionSigs. - * @throws An error if any of the required parameters are missing or if `litActionCode` and `ipfsId` exist at the same time. - */ - getPkpSessionSigs = async (params: GetPkpSessionSigs) => { - const chain = params?.chain || 'ethereum'; - - const pkpSessionSigs = this.getSessionSigs({ - chain, - ...params, - authNeededCallback: async (props: AuthCallbackParams) => { - // -- validate - if (!props.expiration) { - throw new Error( - '[getPkpSessionSigs/callback] expiration is required' - ); - } - - if (!props.resources) { - throw new Error('[getPkpSessionSigs/callback]resources is required'); - } - - if (!props.resourceAbilityRequests) { - throw new Error( - '[getPkpSessionSigs/callback]resourceAbilityRequests is required' - ); - } - - // lit action code and ipfs id cannot exist at the same time - if (props.litActionCode && props.litActionIpfsId) { - throw new Error( - '[getPkpSessionSigs/callback]litActionCode and litActionIpfsId cannot exist at the same time' - ); - } - - /** - * We must provide an empty array for authMethods even if we are not using any auth methods. - * So that the nodes can serialize the request correctly. - */ - const authMethods = params.authMethods || []; - - const response = await this.signSessionKey({ - sessionKey: props.sessionKey, - statement: props.statement || 'Some custom statement.', - authMethods: [...authMethods], - pkpPublicKey: params.pkpPublicKey, - expiration: props.expiration, - resources: props.resources, - chainId: 1, - - // -- required fields - resourceAbilityRequests: props.resourceAbilityRequests, - - // -- optional fields - ...(props.litActionCode && { litActionCode: props.litActionCode }), - ...(props.litActionIpfsId && { - litActionIpfsId: props.litActionIpfsId, - }), - ...(props.jsParams && { jsParams: props.jsParams }), - }); - - return response.authSig; + logError('an error occured, attempting to retry '); + } }, - }); - - return pkpSessionSigs; - }; + this.config.retryTolerance + ); + const requestId = res.requestId; - /** - * Retrieves session signatures specifically for Lit Actions. - * Unlike `getPkpSessionSigs`, this function requires either `litActionCode` or `litActionIpfsId`, and `jsParams` must be provided. - * - * @param params - The parameters required for retrieving the session signatures. - * @returns A promise that resolves with the session signatures. - */ - getLitActionSessionSigs = async (params: GetLitActionSessionSigs) => { - // Check if either litActionCode or litActionIpfsId is provided - if (!params.litActionCode && !params.litActionIpfsId) { - throw new Error( - "Either 'litActionCode' or 'litActionIpfsId' must be provided." - ); + // -- case: promises rejected + if (res.success === false) { + this._throwNodeError(res as RejectedNodePromises, requestId); } - // Check if jsParams is provided - if (!params.jsParams) { - throw new Error("'jsParams' is required."); - } + const signatureShares: NodeBlsSigningShare[] = ( + res as SuccessNodePromises + ).values; - return this.getPkpSessionSigs(params); + log('signatureShares', signatureShares); + + // ========== Result ========== + const finalJwt: string = this.#combineSharesAndGetJWT( + signatureShares, + requestId + ); + + return finalJwt; }; /** * - * Get Session Key URI eg. lit:session:0x1234 + * Decrypt ciphertext with the LIT network. + * + * Endpoint: /web/encryption/sign * - * @param publicKey is the public key of the session key - * @returns { string } the session key uri */ - getSessionKeyUri = (publicKey: string): string => { - return LIT_SESSION_KEY_URI + publicKey; - }; + decrypt = async (params: DecryptRequest): Promise => { + const { sessionSigs, chain, ciphertext, dataToEncryptHash } = params; - /** - * Authenticates an Auth Method for claiming a Programmable Key Pair (PKP). - * A {@link MintCallback} can be defined for custom on chain interactions - * by default the callback will forward to a relay server for minting on chain. - * @param {ClaimKeyRequest} params an Auth Method and {@link MintCallback} - * @returns {Promise} - */ - async claimKeyId( - params: ClaimRequest - ): Promise { + // ========== Validate Params ========== + // -- validate if it's ready if (!this.ready) { const message = - 'LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; + '6 LitNodeClient is not ready. Please call await litNodeClient.connect() first.'; throwError({ message, errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, @@ -2357,124 +2140,148 @@ const resourceAbilityRequests = [ }); } - if (params.authMethod.authMethodType == AuthMethodType.WebAuthn) { - throwError({ - message: - 'Unsupported auth method type. Webauthn, and Lit Actions are not supported for claiming', + // -- validate if this.subnetPubKey is null + if (!this.subnetPubKey) { + const message = 'subnetPubKey cannot be null'; + return throwError({ + message, errorKind: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.kind, errorCode: LIT_ERROR.LIT_NODE_CLIENT_NOT_READY_ERROR.name, }); } - let requestId; + + const paramsIsSafe = safeParams({ + functionName: 'decrypt', + params, + }); + + if (!paramsIsSafe) { + return throwError({ + message: `Parameter validation failed.`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + }); + } + + // ========== Hashing Access Control Conditions ========= + // hash the access control conditions + const hashOfConditions: ArrayBuffer | undefined = + await this.getHashedAccessControlConditions(params); + + if (!hashOfConditions) { + return throwError({ + message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + }); + } + + const hashOfConditionsStr = uint8arrayToString( + new Uint8Array(hashOfConditions), + 'base16' + ); + + // ========== Formatting Access Control Conditions ========= + const { + error, + formattedAccessControlConditions, + formattedEVMContractConditions, + formattedSolRpcConditions, + formattedUnifiedAccessControlConditions, + }: FormattedMultipleAccs = this.getFormattedAccessControlConditions(params); + + if (error) { + throwError({ + message: `You must provide either accessControlConditions or evmContractConditions or solRpcConditions or unifiedAccessControlConditions`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + }); + } + + // ========== Assemble identity parameter ========== + const identityParam = this.#getIdentityParamForEncryption( + hashOfConditionsStr, + dataToEncryptHash + ); + + log('identityParam', identityParam); + + // ========== Get Network Signature ========== const wrapper = async ( id: string ): Promise | RejectedNodePromises> => { - const nodePromises = this.getNodePromises((url: string) => { - if (!params.authMethod) { - throw new Error('authMethod is required'); + const nodePromises = this._getNodePromises((url: string) => { + // -- if session key is available, use it + const authSigToSend = sessionSigs ? sessionSigs[url] : params.authSig; + + if (!authSigToSend) { + return throwError({ + message: `authSig is required`, + errorKind: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.kind, + errorCode: LIT_ERROR.INVALID_ARGUMENT_EXCEPTION.name, + }); } - const reqBody: JsonPKPClaimKeyRequest = { - authMethod: params.authMethod, + const reqBody: EncryptionSignRequest = { + accessControlConditions: formattedAccessControlConditions, + evmContractConditions: formattedEVMContractConditions, + solRpcConditions: formattedSolRpcConditions, + unifiedAccessControlConditions: + formattedUnifiedAccessControlConditions, + dataToEncryptHash, + chain, + authSig: authSigToSend, + epoch: this.currentEpochNumber!, }; - const urlWithPath = composeLitUrl({ + const urlWithParh = composeLitUrl({ url, - endpoint: LIT_ENDPOINT.PKP_CLAIM, + endpoint: LIT_ENDPOINT.ENCRYPTION_SIGN, }); - return this.generatePromise(urlWithPath, reqBody, id); + return this.#generatePromise(urlWithParh, reqBody, id); }); - const responseData = await this.handleNodePromises( + // -- resolve promises + const res = await this._handleNodePromises( nodePromises, id, - this.connectedNodes.size + this.config.minNodeCount ); - - return responseData; + return res; }; - const responseData = await executeWithRetry< + const res = await executeWithRetry< RejectedNodePromises | SuccessNodePromises >( wrapper, - (_error: any, _requestId: string, isFinal: boolean) => { - if (!isFinal) { - logError('an error occured, attempting to retry'); - } + (_error: string, _requestId: string, _isFinal: boolean) => { + logError('an error occured attempting to retry'); }, this.config.retryTolerance ); - requestId = responseData.requestId; - - if (responseData.success === true) { - const nodeSignatures: Signature[] = ( - responseData as SuccessNodePromises - ).values.map((r: any) => { - const sig = ethers.utils.splitSignature(`0x${r.signature}`); - return { - r: sig.r, - s: sig.s, - v: sig.v, - }; - }); - logWithRequestId( - requestId, - `responseData: ${JSON.stringify(responseData, null, 2)}` - ); + const requestId = res.requestId; - const derivedKeyId = (responseData as SuccessNodePromises).values[0] - .derivedKeyId; + // -- case: promises rejected + if (res.success === false) { + this._throwNodeError(res as RejectedNodePromises, requestId); + } - const pubkey: string = this.computeHDPubKey(derivedKeyId); - logWithRequestId( - requestId, - `pubkey ${pubkey} derived from key id ${derivedKeyId}` - ); + const signatureShares: NodeBlsSigningShare[] = ( + res as SuccessNodePromises + ).values; - const relayParams: ClaimRequest<'relay'> = - params as ClaimRequest<'relay'>; + logWithRequestId(requestId, 'signatureShares', signatureShares); - let mintTx = ''; - if (params.mintCallback && 'signer' in params) { - mintTx = await params.mintCallback( - { - derivedKeyId, - authMethodType: params.authMethod.authMethodType, - signatures: nodeSignatures, - pubkey, - signer: (params as ClaimRequest<'client'>).signer, - ...relayParams, - }, - this.config.litNetwork as LitNetwork - ); - } else { - mintTx = await defaultMintClaimCallback( - { - derivedKeyId, - authMethodType: params.authMethod.authMethodType, - signatures: nodeSignatures, - pubkey, - ...relayParams, - }, - this.config.litNetwork as LitNetwork - ); - } + // ========== Result ========== + const decryptedData = this.#decryptWithSignatureShares( + this.subnetPubKey, + uint8arrayFromString(identityParam, 'utf8'), + ciphertext, + signatureShares + ); - return { - signatures: nodeSignatures, - claimedKeyId: derivedKeyId, - pubkey, - mintTx, - }; - } else { - return throwError({ - message: `Claim request has failed. Request trace id: lit_${requestId} `, - errorKind: LIT_ERROR.UNKNOWN_ERROR.kind, - errorCode: LIT_ERROR.UNKNOWN_ERROR.code, - }); - } - } + return { decryptedData }; + }; } diff --git a/packages/lit-node-client/src/lib/lit-node-client.ts b/packages/lit-node-client/src/lib/lit-node-client.ts index 7d84a9c697..935594cbd8 100644 --- a/packages/lit-node-client/src/lib/lit-node-client.ts +++ b/packages/lit-node-client/src/lib/lit-node-client.ts @@ -26,7 +26,7 @@ export class LitNodeClient extends LitNodeClientNodeJs { }); // -- override configs - this.overrideConfigsFromLocalStorage(); + this.#overrideConfigsFromLocalStorage(); } /** @@ -36,7 +36,7 @@ export class LitNodeClient extends LitNodeClientNodeJs { * @returns { void } * */ - overrideConfigsFromLocalStorage = (): void => { + #overrideConfigsFromLocalStorage = (): void => { if (isNode()) return; const storageKey = 'LitNodeClientConfig'; diff --git a/packages/misc/src/lib/misc.spec.ts b/packages/misc/src/lib/misc.spec.ts index 2f0e7e22d9..6130923dd4 100644 --- a/packages/misc/src/lib/misc.spec.ts +++ b/packages/misc/src/lib/misc.spec.ts @@ -288,12 +288,3 @@ it('should not remove hex prefix if it is not present', () => { expect(result).toBe(expectedOutput); }); - -it('should get ip address', async () => { - // polyfill fetch - const fetch = require('node-fetch'); - global.fetch = fetch; - - const ipAddres = await utilsModule.getIpAddress('cayenne.litgateway.com'); - expect(ipAddres).toBe('207.244.70.36'); -}); diff --git a/packages/misc/src/lib/misc.ts b/packages/misc/src/lib/misc.ts index 223ae1df6d..1456dd4c4b 100644 --- a/packages/misc/src/lib/misc.ts +++ b/packages/misc/src/lib/misc.ts @@ -915,26 +915,3 @@ export function normalizeAndStringify(input: string): string { return normalizeAndStringify(unescaped); } } - -/** - * Retrieves the IP address associated with a given domain. - * @param domain - The domain for which to retrieve the IP address. - * @returns A Promise that resolves to the IP address. - * @throws If no IP address is found or if the domain name is invalid. - */ -export async function getIpAddress(domain: string): Promise { - const apiURL = `https://dns.google/resolve?name=${domain}&type=A`; - - try { - const response = await fetch(apiURL); - const data = await response.json(); - - if (data.Answer && data.Answer.length > 0) { - return data.Answer[0].data; - } else { - throw new Error('No IP Address found or bad domain name'); - } - } catch (error: any) { - throw new Error(error); - } -} diff --git a/packages/types/src/lib/ILitNodeClient.ts b/packages/types/src/lib/ILitNodeClient.ts index 0f8b3646b0..d2310b20a5 100644 --- a/packages/types/src/lib/ILitNodeClient.ts +++ b/packages/types/src/lib/ILitNodeClient.ts @@ -6,18 +6,10 @@ import { ExecuteJsResponse, FormattedMultipleAccs, GetSignedTokenRequest, - HandshakeWithNode, - JsonExecutionRequest, JsonExecutionSdkParams, JsonHandshakeResponse, LitNodeClientConfig, MultipleAccessControlConditions, - NodeBlsSigningShare, - NodeCommandResponse, - NodeCommandServerKeysResponse, - RejectedNodePromises, - SendNodeCommand, - SuccessNodePromises, } from './interfaces'; import { ILitResource, ISessionCapabilityObject } from './models'; import { SupportedJsonRequests } from './types'; @@ -36,35 +28,6 @@ export interface ILitNodeClient { // ** IMPORTANT !! You have to create your constructor when implementing this class ** // constructor(customConfig: LitNodeClientConfig); - // ========== Scoped Class Helpers ========== - - /** - * - * Set bootstrapUrls to match the network litNetwork unless it's set to custom - * - * @returns { void } - * - */ - setCustomBootstrapUrls(): void; - - /** - * - * we need to send jwt params iat (issued at) and exp (expiration) because the nodes may have different wall clock times, the nodes will verify that these params are withing a grace period - * - */ - getJWTParams(): { iat: number; exp: number }; - - /** - * - * Combine Shares from signature shares - * - * @param { NodeBlsSigningShare } signatureShares - * - * @returns { string } final JWT (convert the sig to base64 and append to the jwt) - * - */ - combineSharesAndGetJWT(signatureShares: NodeBlsSigningShare[]): string; - /** * * Get different formats of access control conditions, eg. evm, sol, unified etc. @@ -93,60 +56,6 @@ export interface ILitNodeClient { // ========== Promise Handlers ========== - /** - * - * Get and gather node promises - * - * @param { any } callback - * - * @returns { Array> } - * - */ - getNodePromises(callback: Function): Promise[]; - - /** - * Handle node promises - * - * @param { Array> } nodePromises - * - * @param {string} requestId request Id used for logging - * @param {number} minNodeCount The minimum number of nodes we need a successful response from to continue - * @returns { Promise | RejectedNodePromises> } - * - */ - handleNodePromises( - nodePromises: Promise[], - requestId: string, - minNodeCount: number - ): Promise | RejectedNodePromises>; - - /** - * - * Throw node error - * - * @param { RejectedNodePromises } res - * - * @returns { void } - * - */ - _throwNodeError(res: RejectedNodePromises, requestId: string): void; - - // ========== Shares Resolvers ========== - - /** - * - * Get Signature - * - * @param { Array } shareData from all node promises - * - * @returns { string } signature - * - */ - getSignature(shareData: any[], requestId: string): Promise; - - // ========== API Calls to Nodes ========== - sendCommandToNode({ url, data, requestId }: SendNodeCommand): Promise; - /** * * Get JS Execution Shares from Nodes @@ -156,21 +65,6 @@ export interface ILitNodeClient { * @returns { Promise } */ - /** - * - * Handshake with SGX - * - * @param { HandshakeWithNode } params - * - * @returns { Promise } - * - */ - handshakeWithNode( - params: HandshakeWithNode, - requestId: string - ): Promise; - - // ========== Scoped Business Logics ========== /** * * Execute JS on the nodes and combine and return any resulting signatures diff --git a/packages/types/src/lib/interfaces.ts b/packages/types/src/lib/interfaces.ts index 01fc8a0d2c..852f907060 100644 --- a/packages/types/src/lib/interfaces.ts +++ b/packages/types/src/lib/interfaces.ts @@ -1053,9 +1053,6 @@ export interface SignSessionKeyResponse { authSig: AuthSig; } -export interface GetSignSessionKeySharesProp { - body: SessionRequestBody; -} export interface CommonGetSessionSigsProps { pkpPublicKey?: string; @@ -1177,17 +1174,7 @@ export interface LitClientSessionManager { getSessionKey: () => SessionKeyPair; isSessionKeyPair(obj: any): boolean; getExpiration: () => string; - getWalletSig: (getWalletSigProps: GetWalletSigProps) => Promise; - // #authCallbackAndUpdateStorageItem: (params: { - // authCallbackParams: AuthCallbackParams; - // authCallback?: AuthCallback; - // }) => Promise; getPkpSessionSigs: (params: GetPkpSessionSigs) => Promise; - checkNeedToResignSessionKey: (params: { - authSig: AuthSig; - sessionKeyUri: any; - resourceAbilityRequests: LitResourceAbilityRequest[]; - }) => Promise; getSessionSigs: (params: GetSessionSigsProps) => Promise; signSessionKey: ( params: SignSessionKeyProp