diff --git a/package.json b/package.json index 5080c0abe..8040dcd53 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,8 @@ "tslib": "^2.7.0", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1", - "uint8arrays": "^4.0.3" + "uint8arrays": "^4.0.3", + "zod": "^3.23.8" }, "devDependencies": { "@nx/devkit": "17.3.0", 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 d07d82b9f..c191b852f 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 @@ -5,6 +5,9 @@ import { SiweMessage } from 'siwe'; import { LitAccessControlConditionResource, + LitActionResource, + LitPKPResource, + LitRLIResource, LitResourceAbilityRequest, RecapSessionCapabilityObject, createSiweMessage, @@ -15,6 +18,7 @@ import { } from '@lit-protocol/auth-helpers'; import { AUTH_METHOD_TYPE, + CENTRALISATION_BY_NETWORK, EITHER_TYPE, FALLBACK_IPFS_GATEWAYS, GLOBAL_OVERWRITE_IPFS_CODE_BY_NETWORK, @@ -22,6 +26,7 @@ import { InvalidParamType, InvalidSessionSigs, InvalidSignatureError, + LIT_ABILITY, LIT_ACTION_IPFS_HASH, LIT_CURVE, LIT_ENDPOINT, @@ -118,6 +123,7 @@ import type { JsonSignSessionKeyRequestV1, LitClientSessionManager, LitNodeClientConfig, + LitSession, NodeBlsSigningShare, NodeCommandResponse, NodeLog, @@ -136,8 +142,7 @@ import type { export class LitNodeClientNodeJs extends LitCore - implements LitClientSessionManager, ILitNodeClient -{ + implements LitClientSessionManager, ILitNodeClient { defaultAuthCallback?: (authSigParams: AuthCallbackParams) => Promise; // ========== Constructor ========== @@ -661,154 +666,6 @@ export class LitNodeClientNodeJs ); }; - // ========== 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) { - throw new InvalidParamType( - { - info: { - params, - }, - }, - 'targetNodeRange is required' - ); - } - - // 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(); - - log('nodeIndex:', nodeIndex); - - // must be unique & less than bootstrapUrls.length - if ( - !randomSelectedNodeIndexes.includes(nodeIndex) && - nodeIndex < this.config.bootstrapUrls.length - ) { - randomSelectedNodeIndexes.push(nodeIndex); - } - nodeCounter++; - } - - log('Final Selected Indexes:', randomSelectedNodeIndexes); - - const requestId = this._getNewRequestId(); - const nodePromises = []; - - for (let i = 0; i < randomSelectedNodeIndexes.length; i++) { - // should we mix in the jsParams? to do this, we need a canonical way to serialize the jsParams object that will be identical in rust. - // const jsParams = params.jsParams || {}; - // const jsParamsString = JSON.stringify(jsParams); - - const nodeIndex = randomSelectedNodeIndexes[i]; - - // FIXME: we are using this.config.bootstrapUrls to pick the selected node, but we - // should be using something like the list of nodes from the staking contract - // because the staking nodes can change, and the rust code will use the same list - const url = this.config.bootstrapUrls[nodeIndex]; - - log(`running on node ${nodeIndex} at ${url}`); - - // -- choose the right signature - const sessionSig = this.getSessionSigByUrl({ - sessionSigs: params.sessionSigs, - url, - }); - - const reqBody: JsonExecutionRequestTargetNode = { - ...params, - targetNodeRange: params.targetNodeRange, - authSig: sessionSig, - }; - - // this return { url: string, data: JsonRequest } - // const singleNodePromise = this.getJsExecutionShares(url, reqBody, id); - const singleNodePromise = this.sendCommandToNode({ - url: url, - data: params, - requestId: requestId, - }); - - nodePromises.push(singleNodePromise); - } - - return (await this.handleNodePromises( - nodePromises, - requestId, - params.targetNodeRange - )) as SuccessNodePromises | RejectedNodePromises; - }; - // ========== Scoped Business Logics ========== /** @@ -877,6 +734,105 @@ export class LitNodeClientNodeJs return this.generatePromise(urlWithPath, reqBody, requestId); } + + private async _getSessionToken(params: LitSession) { + + const resourceAbilityRequests = params.resources.map(resource => { + switch (resource.type) { + case 'access-control-condition-signing': + return { + resource: new LitAccessControlConditionResource(resource.request), + ability: LIT_ABILITY.AccessControlConditionSigning, + } + case 'access-control-condition-decryption': + return { + resource: new LitAccessControlConditionResource(resource.request), + ability: LIT_ABILITY.AccessControlConditionDecryption, + } + case 'lit-action-execution': + return { + resource: new LitActionResource(resource.request), + ability: LIT_ABILITY.LitActionExecution, + } + case 'rate-limit-increase-auth' + : return { + resource: new LitRLIResource(resource.request), + ability: LIT_ABILITY.RateLimitIncreaseAuth, + } + case 'pkp-signing': + return { + resource: new LitPKPResource(resource.request), + ability: LIT_ABILITY.PKPSigning, + } + default: + throw new Error(`Resource type ${resource.type} is not supported`); + } + }); + + const centralisation = CENTRALISATION_BY_NETWORK[this.config.litNetwork]; + + if (params.type === 'eoa') { + const sessionSigs = await this.getSessionSigs({ + chain: 'ethereum', + resourceAbilityRequests: resourceAbilityRequests, + authNeededCallback: async ({ + uri, + expiration, + resourceAbilityRequests, + }: AuthCallbackParams) => { + console.log('resourceAbilityRequests:', resourceAbilityRequests); + + if (!expiration) { + throw new Error('expiration is required'); + } + + if (!resourceAbilityRequests) { + throw new Error('resourceAbilityRequests is required'); + } + + if (!uri) { + throw new Error('uri is required'); + } + + if (!params.signer) { + throw new Error('signer is required'); + } + + if (!params.capabilityAuthSigs) { + throw new Error('capabilityAuthSigs is required'); + } + + const walletAddress = await params.signer.getAddress(); + + const toSign = await createSiweMessageWithRecaps({ + uri: uri, + expiration: expiration, + resources: resourceAbilityRequests, + walletAddress: walletAddress, + nonce: await this.getLatestBlockhash(), + litNodeClient: this, + }); + + const authSig = await generateAuthSig({ + signer: params.signer, + toSign, + }); + + return authSig; + }, + + ...(centralisation === 'decentralised' && { + capabilityAuthSigs: params.capabilityAuthSigs, + }), + }); + + return sessionSigs; + } + + throw new Error(`Type ${params.type} is not supported yet.`); + + } + /** * * Execute JS on the nodes and combine and return any resulting signatures @@ -889,6 +845,26 @@ export class LitNodeClientNodeJs executeJs = async ( params: JsonExecutionSdkParams ): Promise => { + + if (!params.sessionSigs) { + + if (!params.capabilityAuthSigs) { + throw new Error('capabilityAuthSigs is required'); + } + + const autoSessionSigs = await this._getSessionToken({ + type: 'eoa' || params.sessionType, + resources: [{ + type: 'lit-action-execution', + request: '*', + }], + expiration: params.expiration || LitNodeClientNodeJs.getExpiration(), + capabilityAuthSigs: params.capabilityAuthSigs, + }); + + params.sessionSigs = autoSessionSigs; + } + // ========== Validate Params ========== if (!this.ready) { const message = @@ -1157,8 +1133,8 @@ export class LitNodeClientNodeJs // -- optional params ...(params.authMethods && params.authMethods.length > 0 && { - authMethods: params.authMethods, - }), + authMethods: params.authMethods, + }), }; logWithRequestId(requestId, 'reqBody:', reqBody); @@ -1884,8 +1860,8 @@ export class LitNodeClientNodeJs const sessionCapabilityObject = params.sessionCapabilityObject ? params.sessionCapabilityObject : await this.generateSessionCapabilityObjectWithWildcards( - params.resourceAbilityRequests.map((r) => r.resource) - ); + params.resourceAbilityRequests.map((r) => r.resource) + ); const expiration = params.expiration || LitNodeClientNodeJs.getExpiration(); // -- (TRY) to get the wallet signature @@ -1969,10 +1945,10 @@ export class LitNodeClientNodeJs const capabilities = params.capacityDelegationAuthSig ? [ - ...(params.capabilityAuthSigs ?? []), - params.capacityDelegationAuthSig, - authSig, - ] + ...(params.capabilityAuthSigs ?? []), + params.capacityDelegationAuthSig, + authSig, + ] : [...(params.capabilityAuthSigs ?? []), authSig]; const signingTemplate = { diff --git a/packages/types/src/lib/auth-types.ts b/packages/types/src/lib/auth-types.ts new file mode 100644 index 000000000..f7252bc43 --- /dev/null +++ b/packages/types/src/lib/auth-types.ts @@ -0,0 +1,26 @@ +import { z } from 'zod'; + +/** + * @example + * const obj = ['a', 'b', 'c'] + * ObjectMapFromArray(obj) // { a: 'a', b: 'b', c: 'c' } + */ +const ObjectMapFromArray = (arr: T) => { + return arr.reduce( + (acc, scope) => ({ ...acc, [scope]: scope }), + {} as { [K in T[number]]: K } + ); +}; + +// ----- AUTH RESOURCE TYPES +export const AUTH_RESOURCE_TYPES_VALUES = [ + 'pkp-signing', + 'lit-action-execution', + 'rate-limit-increase-auth', + 'access-control-condition-signing', + 'access-control-condition-decryption', +] as const; + +export const AUTH_RESOURCE_TYPES = ObjectMapFromArray(AUTH_RESOURCE_TYPES_VALUES); +export const AUTH_RESOURCE_TYPES_SCHEMA = z.enum(AUTH_RESOURCE_TYPES_VALUES); +export type AuthResourceTypes = z.infer; \ No newline at end of file diff --git a/packages/types/src/lib/interfaces.ts b/packages/types/src/lib/interfaces.ts index 24f4def9e..37a595574 100644 --- a/packages/types/src/lib/interfaces.ts +++ b/packages/types/src/lib/interfaces.ts @@ -19,6 +19,7 @@ import { SymmetricKey, UnifiedAccessControlConditions, } from './types'; +import { AuthResourceTypes } from './auth-types'; const deprecated = depd('lit-js-sdk:types:interfaces'); @@ -277,7 +278,7 @@ export interface JsonSignChainDataRequest { export interface JsonSignSessionKeyRequestV1 extends Pick, - Pick { + Pick { sessionKey: string; authMethods: AuthMethod[]; pkpPublicKey?: string; @@ -459,9 +460,26 @@ export interface JsonExecutionSdkParamsTargetNode targetNodeRange: number; } +type ISOString = `${string}-${string}-${string}T${string}:${string}:${string}Z` | string; + +export type LitSessionType = 'eoa' | 'pkp' | 'lit-action'; +export interface LitSession { + type: LitSessionType; + resources: { + type: AuthResourceTypes, + request: string | '*'; + }[], + + expiration: ISOString, + + capabilityAuthSigs?: AuthSig[]; + + signer?: T +} + export interface JsonExecutionSdkParams extends Pick, - ExecuteJsAdvancedOptions { + ExecuteJsAdvancedOptions { /** * JS code to run on the nodes */ @@ -475,12 +493,18 @@ export interface JsonExecutionSdkParams /** * the session signatures to use to authorize the user with the nodes */ - sessionSigs: SessionSigsMap; + sessionSigs?: SessionSigsMap; /** * auth methods to resolve */ authMethods?: AuthMethod[]; + + sessionType?: LitSessionType; + + expiration?: ISOString, + + capabilityAuthSigs?: AuthSig[]; } export interface ExecuteJsAdvancedOptions { @@ -537,7 +561,7 @@ export interface SessionSigsOrAuthSig { export interface DecryptRequestBase extends SessionSigsOrAuthSig, - MultipleAccessControlConditions { + MultipleAccessControlConditions { /** * The chain name of the chain that this contract is deployed on. See LIT_CHAINS for currently supported chains. */ @@ -583,7 +607,7 @@ export interface EncryptFileRequest extends DecryptRequestBase { file: AcceptedFileType; } -export interface DecryptRequest extends EncryptResponse, DecryptRequestBase {} +export interface DecryptRequest extends EncryptResponse, DecryptRequestBase { } export interface DecryptResponse { // The decrypted data as a Uint8Array @@ -605,10 +629,10 @@ export interface SigResponse { export interface ExecuteJsResponseBase { signatures: - | { - sig: SigResponse; - } - | any; + | { + sig: SigResponse; + } + | any; } /** @@ -638,7 +662,7 @@ export interface ExecuteJsNoSigningResponse extends ExecuteJsResponseBase { logs: string; } -export interface LitNodePromise {} +export interface LitNodePromise { } export interface SendNodeCommand { url: string; @@ -647,10 +671,10 @@ export interface SendNodeCommand { } export interface SigShare { sigType: - | 'BLS' - | 'K256' - | 'ECDSA_CAIT_SITH' // Legacy alias of K256 - | 'EcdsaCaitSithP256'; + | 'BLS' + | 'K256' + | 'ECDSA_CAIT_SITH' // Legacy alias of K256 + | 'EcdsaCaitSithP256'; signatureShare: string; shareIndex?: number; @@ -1095,7 +1119,7 @@ export interface CommonGetSessionSigsProps { export interface BaseProviderGetSessionSigsProps extends CommonGetSessionSigsProps, - LitActionSdkParams { + LitActionSdkParams { /** * This is a callback that will be used to generate an AuthSig within the session signatures. It's inclusion is required, as it defines the specific resources and abilities that will be allowed for the current session. */ @@ -1104,7 +1128,7 @@ export interface BaseProviderGetSessionSigsProps export interface GetSessionSigsProps extends CommonGetSessionSigsProps, - LitActionSdkParams { + LitActionSdkParams { /** * This is a callback that will be used to generate an AuthSig within the session signatures. It's inclusion is required, as it defines the specific resources and abilities that will be allowed for the current session. */ @@ -1599,7 +1623,7 @@ export interface BaseProviderSessionSigsParams { resourceAbilityRequests?: LitResourceAbilityRequest[]; } -export interface BaseAuthenticateOptions {} +export interface BaseAuthenticateOptions { } export interface EthWalletAuthenticateOptions extends BaseAuthenticateOptions { /** @@ -1665,9 +1689,9 @@ export interface MintCapacityCreditsPerKilosecond } export interface MintCapacityCreditsContext extends MintCapacityCreditsPerDay, - MintCapacityCreditsPerSecond, - MintCapacityCreditsPerKilosecond, - GasLimitParam {} + MintCapacityCreditsPerSecond, + MintCapacityCreditsPerKilosecond, + GasLimitParam { } export interface MintCapacityCreditsRes { rliTxHash: string; capacityTokenId: any; @@ -1790,12 +1814,12 @@ export interface LitActionSdkParams { * An object that contains params to expose to the Lit Action. These will be injected to the JS runtime before your code runs, so you can use any of these as normal variables in your Lit Action. */ jsParams?: - | { - [key: string]: any; - publicKey?: string; - sigName?: string; - } - | any; + | { + [key: string]: any; + publicKey?: string; + sigName?: string; + } + | any; } export interface LitEndpoint { @@ -1817,7 +1841,7 @@ export interface SignerLike { export interface GetPkpSessionSigs extends CommonGetSessionSigsProps, - LitActionSdkParams { + LitActionSdkParams { pkpPublicKey: string; /** @@ -1843,11 +1867,11 @@ export type GetLitActionSessionSigs = CommonGetSessionSigsProps & Pick, 'jsParams'> & ( | (Pick, 'litActionCode'> & { - litActionIpfsId?: never; - }) + litActionIpfsId?: never; + }) | (Pick, 'litActionIpfsId'> & { - litActionCode?: never; - }) + litActionCode?: never; + }) ) & { ipfsOptions?: IpfsOptions; }; diff --git a/yarn.lock b/yarn.lock index 008dcda87..8f7df9ef4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24139,6 +24139,11 @@ yoctocolors-cjs@^2.1.2: resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== +zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== + zwitch@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"