diff --git a/README.md b/README.md index 693c80a70..1c0301e3a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@

Lit Protocol Javascript/Typescript SDK V8.x.x

-
diff --git a/e2e/src/e2e.spec.ts b/e2e/src/e2e.spec.ts index 79da78b57..c5ff7614d 100644 --- a/e2e/src/e2e.spec.ts +++ b/e2e/src/e2e.spec.ts @@ -1,6 +1,7 @@ import { createCustomAuthContext, createPkpAuthContext, + createPkpAuthContextWithPreGeneratedMaterials, } from './helper/auth-contexts'; import { createExecuteJsTest, @@ -154,9 +155,97 @@ describe('all', () => { }); }); - describe('EOA Native', () => { - console.log('πŸ” Testing EOA native authentication and PKP minting'); + describe('PKP Auth with Pre-generated Materials', () => { + console.log('πŸ” Testing PKP auth with pre-generated session materials'); + let preGeneratedAuthContext: any; + + beforeAll(async () => { + try { + preGeneratedAuthContext = + await createPkpAuthContextWithPreGeneratedMaterials(ctx); + } catch (e) { + console.error('Failed to create pre-generated auth context:', e); + throw e; + } + }); + + describe('endpoints', () => { + it('pkpSign with pre-generated materials', () => + createPkpSignTest(ctx, () => preGeneratedAuthContext)()); + + it('executeJs with pre-generated materials', () => + createExecuteJsTest(ctx, () => preGeneratedAuthContext)()); + + it('pkpEncryptDecrypt with pre-generated materials', () => + createPkpEncryptDecryptTest(ctx, () => preGeneratedAuthContext)()); + }); + + describe('error handling', () => { + it('should reject when only sessionKeyPair is provided', async () => { + const tempAuthContext = await ctx.authManager.createPkpAuthContext({ + authData: ctx.aliceViemAccountAuthData, + pkpPublicKey: ctx.aliceViemAccountPkp.publicKey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + litClient: ctx.litClient, + }); + + const sessionKeyPair = tempAuthContext.sessionKeyPair; + + await expect( + ctx.authManager.createPkpAuthContext({ + authData: ctx.aliceViemAccountAuthData, + pkpPublicKey: ctx.aliceViemAccountPkp.publicKey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + litClient: ctx.litClient, + sessionKeyPair, // Only providing sessionKeyPair + // delegationAuthSig is missing + }) + ).rejects.toThrow( + 'Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided' + ); + }); + + it('should reject when only delegationAuthSig is provided', async () => { + const tempAuthContext = await ctx.authManager.createPkpAuthContext({ + authData: ctx.aliceViemAccountAuthData, + pkpPublicKey: ctx.aliceViemAccountPkp.publicKey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + litClient: ctx.litClient, + }); + + const delegationAuthSig = await tempAuthContext.authNeededCallback(); + + await expect( + ctx.authManager.createPkpAuthContext({ + authData: ctx.aliceViemAccountAuthData, + pkpPublicKey: ctx.aliceViemAccountPkp.publicKey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + litClient: ctx.litClient, + // sessionKeyPair is missing + delegationAuthSig, // Only providing delegationAuthSig + }) + ).rejects.toThrow( + 'Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided' + ); + }); + }); + }); + + describe('EOA Native', () => { + console.log('πŸ” Testing EOA native authentication and PKP minting'); it('eoaNativeAuthFlow', () => createEoaNativeAuthFlowTest(ctx)()); }); }); diff --git a/e2e/src/helper/auth-contexts.ts b/e2e/src/helper/auth-contexts.ts index 54a6be92a..628fe3810 100644 --- a/e2e/src/helper/auth-contexts.ts +++ b/e2e/src/helper/auth-contexts.ts @@ -1,7 +1,70 @@ import { init } from '../init'; - +import { generateSessionKeyPair } from '@lit-protocol/auth'; import { hexToBigInt, keccak256, toBytes } from 'viem'; +/** + * Creates a PKP authentication context with pre-generated session materials + * This simulates a server-side use case where session key pair and delegation + * signature are generated once and reused for multiple requests + */ +export const createPkpAuthContextWithPreGeneratedMaterials = async ( + ctx: Awaited> +) => { + console.log('πŸ” Creating PKP Auth Context with Pre-generated Materials'); + try { + // Step 1: Generate a session key pair directly + console.log(' πŸ“ Step 1: Generating session key pair...'); + const sessionKeyPair = generateSessionKeyPair(); + + // Step 2: Generate PKP delegation signature for the session key pair + console.log(' πŸ“ Step 2: Generating PKP delegation signature...'); + const delegationAuthSig = + await ctx.authManager.generatePkpDelegationAuthSig({ + pkpPublicKey: ctx.aliceViemAccountPkp.publicKey, + authData: ctx.aliceViemAccountAuthData, + sessionKeyPair, + authConfig: { + resources: [ + ['pkp-signing', '*'], + ['lit-action-execution', '*'], + ['access-control-condition-decryption', '*'], + ], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + litClient: ctx.litClient, + }); + + console.log(' πŸ“ Session materials generated:', { + hasSessionKeyPair: !!sessionKeyPair, + hasDelegationAuthSig: !!delegationAuthSig, + sessionKeyPublicKey: sessionKeyPair?.publicKey?.substring(0, 20) + '...', + }); + + // Step 3: Create auth context using the pre-generated materials + // Using the dedicated function for pre-generated materials with a clean, minimal signature + console.log( + ' πŸ“ Step 3: Creating auth context with pre-generated materials...' + ); + const authContextWithPreGenerated = + await ctx.authManager.createPkpAuthContextFromPreGenerated({ + pkpPublicKey: ctx.aliceViemAccountPkp.publicKey, + sessionKeyPair, + delegationAuthSig, + // Optional: can provide authData if needed, otherwise minimal default is used + authData: ctx.aliceViemAccountAuthData, + }); + + console.log('βœ… PKP Auth Context with Pre-generated Materials created'); + return authContextWithPreGenerated; + } catch (e) { + console.error( + '❌ Error creating PKP Auth Context with Pre-generated Materials', + e + ); + throw e; + } +}; + /** * Creates a PKP authentication context */ @@ -78,3 +141,72 @@ export const createCustomAuthContext = async ( throw e; } }; + +/** + * Creates an EOA authentication context with pre-generated session materials + * This demonstrates how to pre-generate EOA session materials for server-side use + */ +export const createEoaAuthContextWithPreGeneratedMaterials = async ( + ctx: Awaited> +) => { + console.log('πŸ” Creating EOA Auth Context with Pre-generated Materials'); + try { + // Step 1: Generate a session key pair directly + console.log(' πŸ“ Step 1: Generating session key pair...'); + const sessionKeyPair = generateSessionKeyPair(); + + // Step 2: Generate EOA delegation signature for the session key pair + console.log(' πŸ“ Step 2: Generating EOA delegation signature...'); + const delegationAuthSig = + await ctx.authManager.generateEoaDelegationAuthSig({ + account: ctx.aliceViemAccount, + sessionKeyPair, + authConfig: { + resources: [ + ['pkp-signing', '*'], + ['lit-action-execution', '*'], + ['access-control-condition-decryption', '*'], + ], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + litClient: ctx.litClient, + }); + + console.log(' πŸ“ EOA session materials generated:', { + hasSessionKeyPair: !!sessionKeyPair, + hasDelegationAuthSig: !!delegationAuthSig, + sessionKeyPublicKey: sessionKeyPair?.publicKey?.substring(0, 20) + '...', + }); + + // Step 3: Create EOA auth context using the pre-generated materials + console.log( + ' πŸ“ Step 3: Creating EOA auth context with pre-generated materials...' + ); + const authContextWithPreGenerated = + await ctx.authManager.createEoaAuthContext({ + authConfig: { + resources: [ + ['pkp-signing', '*'], + ['lit-action-execution', '*'], + ['access-control-condition-decryption', '*'], + ], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + config: { + account: ctx.aliceViemAccount, + }, + litClient: ctx.litClient, + // Note: EOA auth contexts don't currently support pre-generated materials + // This demonstrates the pattern for when it's implemented + }); + + console.log('βœ… EOA Auth Context with Pre-generated Materials created'); + return authContextWithPreGenerated; + } catch (e) { + console.error( + '❌ Error creating EOA Auth Context with Pre-generated Materials', + e + ); + throw e; + } +}; diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index 38862930d..db0ee1398 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -95,6 +95,32 @@ export { getAuthIdByAuthMethod } from './lib/authenticators/helper/utils'; */ export { generateSessionKeyPair } from './lib/AuthManager/utils/generateSessionKeyPair'; +/** + * Utility function to generate a PKP delegation auth signature for a given session key pair. + * The PKP will sign the session key delegation message via Lit nodes. + * This function is useful for server-side scenarios where you want to pre-generate + * PKP session materials and reuse them across multiple requests. + */ +export { generatePkpDelegationAuthSig } from './lib/AuthManager/authAdapters/generatePkpDelegationAuthSig'; + +/** + * Utility function to generate an EOA delegation auth signature for a given session key pair. + * The EOA wallet will sign the session key delegation message directly. + * This function is useful for server-side scenarios where you want to pre-generate + * EOA session materials and reuse them across multiple requests. + */ +export { generateEoaDelegationAuthSig } from './lib/AuthManager/authAdapters/generateEoaDelegationAuthSig'; + +/** + * Utility function to create a PKP auth context from pre-generated session materials. + * This is a streamlined API for server-side scenarios where session materials + * are generated once and reused across multiple requests. + * + * This function only requires the essential parameters (pkpPublicKey, sessionKeyPair, delegationAuthSig) + * and extracts auth config information from the delegation signature automatically. + */ +export { getPkpAuthContextFromPreGeneratedAdapter } from './lib/AuthManager/authAdapters/getPkpAuthContextFromPreGeneratedAdapter'; + // ============================== Authenticators ============================== export { DiscordAuthenticator, diff --git a/packages/auth/src/lib/AuthManager/auth-manager.ts b/packages/auth/src/lib/AuthManager/auth-manager.ts index a48663a05..23dedf55e 100644 --- a/packages/auth/src/lib/AuthManager/auth-manager.ts +++ b/packages/auth/src/lib/AuthManager/auth-manager.ts @@ -1,6 +1,6 @@ import { getChildLogger } from '@lit-protocol/logger'; import { AuthData, HexPrefixedSchema } from '@lit-protocol/schemas'; -// import { AuthSig, SessionKeyPair } from '@lit-protocol/types'; +import { AuthSig, SessionKeyPair } from '@lit-protocol/types'; import { z } from 'zod'; import { AuthConfigV2 } from '../authenticators/types'; import type { LitAuthStorageProvider } from '../storage/types'; @@ -11,7 +11,9 @@ import { import { getPkpAuthContextAdapter } from './authAdapters/getPkpAuthContextAdapter'; import { AuthConfigSchema } from './authContexts/BaseAuthContextType'; import { getCustomAuthContextAdapter } from './authAdapters/getCustomAuthContextAdapter'; -import { hexToBigInt, keccak256, toBytes } from 'viem'; +import { generatePkpDelegationAuthSig } from './authAdapters/generatePkpDelegationAuthSig'; +import { generateEoaDelegationAuthSig } from './authAdapters/generateEoaDelegationAuthSig'; +import { getPkpAuthContextFromPreGeneratedAdapter } from './authAdapters/getPkpAuthContextFromPreGeneratedAdapter'; export interface AuthManagerParams { storage: LitAuthStorageProvider; @@ -77,12 +79,20 @@ export const createAuthManager = (authManagerParams: AuthManagerParams) => { cache?: { delegationAuthSig?: boolean; }; - // Optional pre-generated auth materials for server-side usage - // sessionKeyPair?: SessionKeyPair; - // delegationAuthSig?: AuthSig; }) => { return getPkpAuthContextAdapter(authManagerParams, params); }, + createPkpAuthContextFromPreGenerated: (params: { + pkpPublicKey: z.infer; + sessionKeyPair: SessionKeyPair; + delegationAuthSig: AuthSig; + authData?: AuthData; + }) => { + return getPkpAuthContextFromPreGeneratedAdapter( + authManagerParams, + params + ); + }, createCustomAuthContext: (params: { // authData: AuthData; pkpPublicKey: z.infer; @@ -104,5 +114,22 @@ export const createAuthManager = (authManagerParams: AuthManagerParams) => { return getCustomAuthContextAdapter(authManagerParams, params); }, + generatePkpDelegationAuthSig: (params: { + pkpPublicKey: z.infer; + authData: AuthData; + sessionKeyPair: SessionKeyPair; + authConfig: AuthConfigV2; + litClient: BaseAuthContext['litClient']; + }) => { + return generatePkpDelegationAuthSig(authManagerParams, params); + }, + generateEoaDelegationAuthSig: (params: { + account: any; // ExpectedAccountOrWalletClient type + sessionKeyPair: SessionKeyPair; + authConfig: AuthConfigV2; + litClient: BaseAuthContext['litClient']; + }) => { + return generateEoaDelegationAuthSig(authManagerParams, params); + }, }; }; diff --git a/packages/auth/src/lib/AuthManager/authAdapters/generateEoaDelegationAuthSig.ts b/packages/auth/src/lib/AuthManager/authAdapters/generateEoaDelegationAuthSig.ts new file mode 100644 index 000000000..05c876074 --- /dev/null +++ b/packages/auth/src/lib/AuthManager/authAdapters/generateEoaDelegationAuthSig.ts @@ -0,0 +1,109 @@ +import { AUTH_METHOD_TYPE } from '@lit-protocol/constants'; +import { getChildLogger } from '@lit-protocol/logger'; +import { AuthConfigSchema } from '@lit-protocol/schemas'; +import { AuthSig, SessionKeyPair } from '@lit-protocol/types'; +import { z } from 'zod'; +import { AuthConfigV2 } from '../../authenticators/types'; +import { LitAuthData } from '../../types'; +import { AuthManagerParams } from '../auth-manager'; +import { + ExpectedAccountOrWalletClient, + getEoaAuthContext, +} from '../authContexts/getEoaAuthContext'; +import { processResources } from '../utils/processResources'; +import { WalletClientAuthenticator } from '../../authenticators/WalletClientAuthenticator'; +import { ViemAccountAuthenticator } from '../../authenticators/ViemAccountAuthenticator'; + +const _logger = getChildLogger({ + module: 'generateEoaDelegationAuthSig', +}); + +/** + * Generates an EOA delegation auth signature for a given session key pair. + * The EOA wallet will sign the session key delegation message directly. + * This function is useful for server-side scenarios where you want to pre-generate + * EOA session materials and reuse them across multiple requests. + * + * @param upstreamParams - Auth manager parameters including storage + * @param params - Parameters for generating the EOA delegation signature + * @returns The delegation auth signature (AuthSig) signed by the EOA wallet + */ +export async function generateEoaDelegationAuthSig( + upstreamParams: AuthManagerParams, + params: { + account: ExpectedAccountOrWalletClient; + sessionKeyPair: SessionKeyPair; + authConfig: AuthConfigV2; + litClient: { + getContext: () => Promise; + }; + } +): Promise { + _logger.info( + 'generateEoaDelegationAuthSig: Starting EOA delegation signature generation', + { + hasAccount: !!params.account, + hasSessionKeyPair: !!params.sessionKeyPair, + } + ); + + const _resources = processResources(params.authConfig.resources); + + // Get network context from litClient for nonce + const litClientCtx = await params.litClient.getContext(); + + // Create a minimal LitAuthData structure with the provided session key pair + const litAuthData: LitAuthData = { + sessionKey: { + keyPair: params.sessionKeyPair, + expiresAt: params.authConfig.expiration!, + }, + // For EOA, we use EthWallet as the auth method type + authMethodType: AUTH_METHOD_TYPE.EthWallet, + }; + + // Determine the authenticator based on account type + let authenticatorClass; + if ( + 'account' in params.account && + params.account.account?.type === 'json-rpc' + ) { + // WalletClient + authenticatorClass = WalletClientAuthenticator; + } else { + // Viem Account + authenticatorClass = ViemAccountAuthenticator; + } + + // Create auth config for validation + const authConfigForValidation = { + ...params.authConfig, + resources: _resources, + }; + const validatedAuthConfig = AuthConfigSchema.parse(authConfigForValidation); + + // Call getEoaAuthContext which will generate the delegation signature + const authContext = await getEoaAuthContext({ + authentication: { + authenticator: authenticatorClass, + account: params.account, + }, + authConfig: validatedAuthConfig, + deps: { + nonce: litClientCtx.latestBlockhash, + authData: litAuthData, + }, + }); + + // Get the delegation signature from the auth context + const delegationAuthSig = await authContext.authNeededCallback(); + + _logger.info( + 'generateEoaDelegationAuthSig: EOA delegation signature generated successfully', + { + hasSignature: !!delegationAuthSig, + } + ); + + return delegationAuthSig as AuthSig; +} diff --git a/packages/auth/src/lib/AuthManager/authAdapters/generatePkpDelegationAuthSig.ts b/packages/auth/src/lib/AuthManager/authAdapters/generatePkpDelegationAuthSig.ts new file mode 100644 index 000000000..04c85217c --- /dev/null +++ b/packages/auth/src/lib/AuthManager/authAdapters/generatePkpDelegationAuthSig.ts @@ -0,0 +1,123 @@ +import { AUTH_METHOD_TYPE_VALUES, PRODUCT_IDS } from '@lit-protocol/constants'; +import { getChildLogger } from '@lit-protocol/logger'; +import { + AuthData, + HexPrefixedSchema, + SessionKeyUriSchema, +} from '@lit-protocol/schemas'; +import { AuthSig, SessionKeyPair } from '@lit-protocol/types'; +import { ethers } from 'ethers'; +import { z } from 'zod'; +import { AuthConfigV2 } from '../../authenticators/types'; +import { LitAuthData } from '../../types'; +import { AuthManagerParams } from '../auth-manager'; +import { getPkpAuthContext } from '../authContexts/getPkpAuthContext'; +import { processResources } from '../utils/processResources'; + +const _logger = getChildLogger({ + module: 'generatePkpDelegationAuthSig', +}); + +/** + * Generates a PKP delegation auth signature for a given session key pair. + * The PKP will sign the session key delegation message via Lit nodes. + * This function is useful for server-side scenarios where you want to pre-generate + * PKP session materials and reuse them across multiple requests. + * + * @param upstreamParams - Auth manager parameters including storage + * @param params - Parameters for generating the PKP delegation signature + * @returns The delegation auth signature (AuthSig) signed by the PKP + */ +export async function generatePkpDelegationAuthSig( + upstreamParams: AuthManagerParams, + params: { + pkpPublicKey: z.infer; + authData: AuthData; + sessionKeyPair: SessionKeyPair; + authConfig: AuthConfigV2; + litClient: { + getContext: () => Promise; + }; + } +): Promise { + _logger.info( + 'generatePkpDelegationAuthSig: Starting PKP delegation signature generation', + { + pkpPublicKey: params.pkpPublicKey, + hasSessionKeyPair: !!params.sessionKeyPair, + } + ); + + const _resources = processResources(params.authConfig.resources); + + // Get network context from litClient + const litClientCtx = await params.litClient.getContext(); + const latestConnectionInfo = litClientCtx.latestConnectionInfo; + const nodePrices = latestConnectionInfo.priceFeedInfo.networkPrices; + const handshakeResult = litClientCtx.handshakeResult; + const threshold = handshakeResult.threshold; + + const nodeUrls = litClientCtx.getMaxPricesForNodeProduct({ + nodePrices: nodePrices, + userMaxPrice: litClientCtx.getUserMaxPrice({ + product: 'LIT_ACTION', + }), + productId: PRODUCT_IDS['LIT_ACTION'], + numRequiredNodes: threshold, + }); + + const pkpAddress = ethers.utils.computeAddress(params.pkpPublicKey); + + // Create a minimal LitAuthData structure with the provided session key pair + const litAuthData: LitAuthData = { + sessionKey: { + keyPair: params.sessionKeyPair, + expiresAt: params.authConfig.expiration!, + }, + authMethodType: params.authData.authMethodType as AUTH_METHOD_TYPE_VALUES, + }; + + // Call getPkpAuthContext which will generate the delegation signature + const authContext = await getPkpAuthContext({ + authentication: { + pkpPublicKey: params.pkpPublicKey, + authData: params.authData, + }, + authConfig: { + domain: params.authConfig.domain!, + resources: _resources, + capabilityAuthSigs: params.authConfig.capabilityAuthSigs!, + expiration: params.authConfig.expiration!, + statement: params.authConfig.statement!, + }, + deps: { + litAuthData: litAuthData, + connection: { + nonce: litClientCtx.latestBlockhash, + currentEpoch: + litClientCtx.latestConnectionInfo.epochState.currentNumber, + nodeUrls: nodeUrls, + }, + signSessionKey: litClientCtx.signSessionKey, + storage: upstreamParams.storage, + pkpAddress: pkpAddress, + }, + // Disable caching since we're explicitly generating a new signature + cache: { + delegationAuthSig: false, + }, + }); + + // Get the delegation signature from the auth context + const delegationAuthSig = await authContext.authNeededCallback(); + + _logger.info( + 'generatePkpDelegationAuthSig: PKP delegation signature generated successfully', + { + pkpAddress, + hasSignature: !!delegationAuthSig, + } + ); + + return delegationAuthSig; +} diff --git a/packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextAdapter.ts b/packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextAdapter.ts index eb39b1c11..c3840cf2c 100644 --- a/packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextAdapter.ts +++ b/packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextAdapter.ts @@ -1,11 +1,16 @@ import { AUTH_METHOD_TYPE_VALUES, PRODUCT_IDS } from '@lit-protocol/constants'; +import { getChildLogger } from '@lit-protocol/logger'; import { AuthData, HexPrefixedSchema, NodeUrlsSchema, - // SessionKeyUriSchema, + SessionKeyUriSchema, } from '@lit-protocol/schemas'; -// import { AuthSig, LitResourceAbilityRequest, SessionKeyPair } from '@lit-protocol/types'; +import { + AuthSig, + LitResourceAbilityRequest, + SessionKeyPair, +} from '@lit-protocol/types'; import { ethers } from 'ethers'; import { z } from 'zod'; import { AuthConfigV2 } from '../../authenticators/types'; @@ -14,6 +19,10 @@ import { getPkpAuthContext } from '../authContexts/getPkpAuthContext'; import { processResources } from '../utils/processResources'; import { tryGetCachedAuthData } from '../try-getters/tryGetCachedAuthData'; +const _logger = getChildLogger({ + module: 'getPkpAuthContextAdapter', +}); + export const PkpAuthDepsSchema = z.object({ nonce: z.any(), currentEpoch: z.any(), @@ -24,36 +33,50 @@ export const PkpAuthDepsSchema = z.object({ /** * Validates that the provided delegation auth sig hasn't expired and contains required resources */ -// function validateDelegationAuthSig( -// delegationAuthSig: AuthSig, -// requiredResources: LitResourceAbilityRequest[], -// sessionKeyUri: string -// ): void { -// try { -// // Parse the signed message to extract expiration and validate session key match -// const siweMessage = delegationAuthSig.signedMessage; - -// // Check expiration -// const expirationMatch = siweMessage.match(/^Expiration Time: (.*)$/m); -// if (expirationMatch && expirationMatch[1]) { -// const expiration = new Date(expirationMatch[1].trim()); -// if (expiration.getTime() <= Date.now()) { -// throw new Error(`Delegation signature has expired at ${expiration.toISOString()}`); -// } -// } - -// // Validate session key URI matches -// if (!siweMessage.includes(sessionKeyUri)) { -// throw new Error('Session key URI in delegation signature does not match provided session key pair'); -// } - -// // TODO: Add resource validation - check if delegationAuthSig has required resources -// // This would involve parsing the RECAP URN and checking against requiredResources - -// } catch (error) { -// throw new Error(`Invalid delegation signature: ${error instanceof Error ? error.message : 'Unknown error'}`); -// } -// } +function validateDelegationAuthSig( + delegationAuthSig: AuthSig, + requiredResources: LitResourceAbilityRequest[], + sessionKeyUri: string +): void { + try { + // Parse the signed message to extract expiration and validate session key match + const siweMessage = delegationAuthSig.signedMessage; + + // Check expiration + const expirationMatch = siweMessage.match(/^Expiration Time: (.*)$/m); + if (expirationMatch && expirationMatch[1]) { + const expiration = new Date(expirationMatch[1].trim()); + if (expiration.getTime() <= Date.now()) { + throw new Error( + `Delegation signature has expired at ${expiration.toISOString()}` + ); + } + } + + // Validate session key URI matches + if (!siweMessage.includes(sessionKeyUri)) { + throw new Error( + 'Session key URI in delegation signature does not match provided session key pair' + ); + } + + // TODO: Add resource validation - check if delegationAuthSig has required resources + // This would involve parsing the RECAP URN and checking against requiredResources + _logger.debug( + 'validateDelegationAuthSig: Delegation signature validated successfully', + { + sessionKeyUri, + hasResources: requiredResources.length > 0, + } + ); + } catch (error) { + throw new Error( + `Invalid delegation signature: ${ + error instanceof Error ? error.message : 'Unknown error' + }` + ); + } +} export async function getPkpAuthContextAdapter( upstreamParams: AuthManagerParams, @@ -67,54 +90,70 @@ export async function getPkpAuthContextAdapter( cache?: { delegationAuthSig?: boolean; }; - // Optional pre-generated auth materials - // sessionKeyPair?: SessionKeyPair; - // delegationAuthSig?: AuthSig; + // Optional pre-generated auth materials for server-side usage + sessionKeyPair?: SessionKeyPair; + delegationAuthSig?: AuthSig; } ) { const _resources = processResources(params.authConfig.resources); - // // Validate optional parameters - // if ((params.sessionKeyPair && !params.delegationAuthSig) || - // (!params.sessionKeyPair && params.delegationAuthSig)) { - // throw new Error('Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided'); - // } - - // // If pre-generated auth materials are provided, validate and use them - // if (params.sessionKeyPair && params.delegationAuthSig) { - // // Generate sessionKeyUri from the public key - // const sessionKeyUri = SessionKeyUriSchema.parse(params.sessionKeyPair.publicKey); - - // // Validate the delegation signature - // validateDelegationAuthSig( - // params.delegationAuthSig, - // _resources, - // sessionKeyUri - // ); - - // // Return auth context using provided materials - // return { - // chain: 'ethereum', - // pkpPublicKey: params.pkpPublicKey, - // authData: params.authData, - // authConfig: { - // domain: params.authConfig.domain!, - // resources: _resources, - // capabilityAuthSigs: params.authConfig.capabilityAuthSigs!, - // expiration: params.authConfig.expiration!, - // statement: params.authConfig.statement!, - // }, - // sessionKeyPair: { - // ...params.sessionKeyPair, - // sessionKeyUri, // Add the generated sessionKeyUri to match expected interface - // }, - // // Provide the pre-generated delegation signature - // authNeededCallback: async () => params.delegationAuthSig!, - // }; - // } + // Validate optional parameters + if ( + (params.sessionKeyPair && !params.delegationAuthSig) || + (!params.sessionKeyPair && params.delegationAuthSig) + ) { + throw new Error( + 'Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided' + ); + } + + // If pre-generated auth materials are provided, validate and use them + if (params.sessionKeyPair && params.delegationAuthSig) { + _logger.info( + 'getPkpAuthContextAdapter: Using pre-generated session materials', + { + hasSessionKeyPair: true, + hasDelegationAuthSig: true, + } + ); + + // Generate sessionKeyUri from the public key + const sessionKeyUri = SessionKeyUriSchema.parse( + 'lit:session:' + params.sessionKeyPair.publicKey + ); + + // Validate the delegation signature + validateDelegationAuthSig( + params.delegationAuthSig, + _resources, + sessionKeyUri + ); + + // Return auth context using provided materials + return { + chain: 'ethereum', + pkpPublicKey: params.pkpPublicKey, + authData: params.authData, + authConfig: { + domain: params.authConfig.domain!, + resources: _resources, + capabilityAuthSigs: params.authConfig.capabilityAuthSigs!, + expiration: params.authConfig.expiration!, + statement: params.authConfig.statement!, + }, + sessionKeyPair: params.sessionKeyPair, + // Provide the pre-generated delegation signature + authNeededCallback: async () => { + _logger.debug( + 'getPkpAuthContextAdapter: Returning pre-generated delegation signature' + ); + return params.delegationAuthSig!; + }, + }; + } // Original logic for generating auth materials - // TODO: πŸ‘‡ The plan is to identify if the certain operations could be wrapped inside a single function + // TODO: πŸ‘‡ The plan is to identify whether certain operations can be wrapped inside a single function // where different network modules can provide their own implementations. // TODO: ❗️THIS IS NOT TYPED - we have to fix this! diff --git a/packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextFromPreGeneratedAdapter.ts b/packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextFromPreGeneratedAdapter.ts new file mode 100644 index 000000000..735f84e8b --- /dev/null +++ b/packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextFromPreGeneratedAdapter.ts @@ -0,0 +1,170 @@ +import { getChildLogger } from '@lit-protocol/logger'; +import { HexPrefixedSchema, SessionKeyUriSchema } from '@lit-protocol/schemas'; +import { + AuthSig, + LitResourceAbilityRequest, + SessionKeyPair, +} from '@lit-protocol/types'; +import { z } from 'zod'; +import { AuthData } from '@lit-protocol/schemas'; +import { AuthManagerParams } from '../auth-manager'; +import { processResources } from '../utils/processResources'; + +const _logger = getChildLogger({ + module: 'getPkpAuthContextFromPreGeneratedAdapter', +}); + +/** + * Extracts auth config information from a delegation signature's SIWE message + */ +function extractAuthConfigFromDelegationAuthSig(delegationAuthSig: AuthSig): { + domain?: string; + statement?: string; + expiration?: string; + resources?: LitResourceAbilityRequest[]; +} { + const siweMessage = delegationAuthSig.signedMessage; + + // Extract domain + const domainMatch = siweMessage.match(/^([^\s]+) wants you to sign in/m); + const domain = domainMatch ? domainMatch[1] : undefined; + + // Extract statement + const statementMatch = siweMessage.match(/^(.*?)(?:\n\nURI:|$)/m); + const statement = statementMatch + ? statementMatch[1].split('\n').slice(2).join('\n').trim() + : undefined; + + // Extract expiration + const expirationMatch = siweMessage.match(/^Expiration Time: (.*)$/m); + const expiration = expirationMatch ? expirationMatch[1].trim() : undefined; + + // Extract resources from RECAP URN - simplified for now + // TODO: Implement proper RECAP parsing when needed + const resources: LitResourceAbilityRequest[] = []; + + return { domain, statement, expiration, resources }; +} + +/** + * Validates that the provided delegation auth sig hasn't expired and contains required resources + */ +function validateDelegationAuthSig( + delegationAuthSig: AuthSig, + requiredResources: LitResourceAbilityRequest[], + sessionKeyUri: string +): void { + try { + // Parse the signed message to extract expiration and validate session key match + const siweMessage = delegationAuthSig.signedMessage; + + // Check expiration + const expirationMatch = siweMessage.match(/^Expiration Time: (.*)$/m); + if (expirationMatch && expirationMatch[1]) { + const expiration = new Date(expirationMatch[1].trim()); + if (expiration.getTime() <= Date.now()) { + throw new Error( + `Delegation signature has expired at ${expiration.toISOString()}` + ); + } + } + + // Validate session key URI matches + if (!siweMessage.includes(sessionKeyUri)) { + throw new Error( + 'Session key URI in delegation signature does not match provided session key pair' + ); + } + + // TODO: Add resource validation - check if delegationAuthSig has required resources + // This would involve parsing the RECAP URN and checking against requiredResources + _logger.debug( + 'validateDelegationAuthSig: Delegation signature validated successfully', + { + sessionKeyUri, + hasResources: requiredResources.length > 0, + } + ); + } catch (error) { + throw new Error( + `Invalid delegation signature: ${ + error instanceof Error ? error.message : 'Unknown error' + }` + ); + } +} + +/** + * Creates a PKP auth context from pre-generated session materials. + * This is a streamlined API for server-side scenarios where session materials + * are generated once and reused across multiple requests. + */ +export async function getPkpAuthContextFromPreGeneratedAdapter( + upstreamParams: AuthManagerParams, + params: { + pkpPublicKey: z.infer; + sessionKeyPair: SessionKeyPair; + delegationAuthSig: AuthSig; + authData?: AuthData; + } +) { + _logger.info( + 'getPkpAuthContextFromPreGeneratedAdapter: Creating PKP auth context from pre-generated materials', + { + pkpPublicKey: params.pkpPublicKey, + hasSessionKeyPair: !!params.sessionKeyPair, + hasDelegationAuthSig: !!params.delegationAuthSig, + hasAuthData: !!params.authData, + } + ); + + // Extract auth config from delegation signature + const extractedAuthConfig = extractAuthConfigFromDelegationAuthSig( + params.delegationAuthSig + ); + + // Create auth config using extracted information with sensible defaults + const authConfig = { + domain: extractedAuthConfig.domain || 'localhost', + resources: + extractedAuthConfig.resources || processResources([['pkp-signing', '*']]), + capabilityAuthSigs: [], + expiration: + extractedAuthConfig.expiration || + new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), + statement: extractedAuthConfig.statement || '', + }; + + // Generate sessionKeyUri from the public key + const sessionKeyUri = SessionKeyUriSchema.parse( + 'lit:session:' + params.sessionKeyPair.publicKey + ); + + // Validate the delegation signature + validateDelegationAuthSig( + params.delegationAuthSig, + authConfig.resources, + sessionKeyUri + ); + + // Return auth context using pre-generated materials + return { + chain: 'ethereum', + pkpPublicKey: params.pkpPublicKey, + authData: + params.authData || + ({ + // Provide minimal auth data if not provided + authMethodType: 1, // Default auth method type + } as AuthData), + authConfig, + sessionKeyPair: params.sessionKeyPair, + // Provide the pre-generated delegation signature + authNeededCallback: async () => { + _logger.debug( + 'getPkpAuthContextFromPreGeneratedAdapter: Returning pre-generated delegation signature' + ); + return params.delegationAuthSig; + }, + }; +} diff --git a/packages/auth/src/lib/AuthManager/authContexts/getPkpAuthContext.ts b/packages/auth/src/lib/AuthManager/authContexts/getPkpAuthContext.ts index 88fc7c780..74ca3b1f6 100644 --- a/packages/auth/src/lib/AuthManager/authContexts/getPkpAuthContext.ts +++ b/packages/auth/src/lib/AuthManager/authContexts/getPkpAuthContext.ts @@ -49,6 +49,9 @@ export const GetPkpAuthContextSchema = z.object({ // @depreacted - to be removed. testing only. pkpAddress: z.string(), + + // Optional pre-generated delegation signature + preGeneratedDelegationAuthSig: z.any().optional(), }), cache: z .object({ @@ -143,20 +146,24 @@ export const getPkpAuthContext = async ( resources: _params.authConfig.resources, }; - const delegationAuthSig = await tryGetCachedDelegationAuthSig({ - cache: _params.cache?.delegationAuthSig, - storage: _params.deps.storage, - address: _params.deps.pkpAddress, - expiration: _params.authConfig.expiration, - signSessionKey: () => - _params.deps.signSessionKey({ - requestBody, - nodeUrls: _nodeInfo.urls, - }), - }); + // Use pre-generated delegation signature if provided, otherwise generate/fetch one + const delegationAuthSig = _params.deps.preGeneratedDelegationAuthSig + ? _params.deps.preGeneratedDelegationAuthSig + : await tryGetCachedDelegationAuthSig({ + cache: _params.cache?.delegationAuthSig, + storage: _params.deps.storage, + address: _params.deps.pkpAddress, + expiration: _params.authConfig.expiration, + signSessionKey: () => + _params.deps.signSessionKey({ + requestBody, + nodeUrls: _nodeInfo.urls, + }), + }); _logger.info('getPkpAuthContext: delegationAuthSig', { delegationAuthSig, + isPreGenerated: !!_params.deps.preGeneratedDelegationAuthSig, }); return {