From 38eca236cdccfa3e01d5cf8263c6e73e56ca28f1 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 17 Jul 2024 16:00:27 -0400 Subject: [PATCH 01/21] Simplify calls to retrieveAndDecrypt and retrieve. We don't need to get the ritual so early; obtaining it later saves us from passing additional parameters. --- packages/taco/src/taco.ts | 4 +--- packages/taco/src/tdec.ts | 15 ++++++--------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/taco/src/taco.ts b/packages/taco/src/taco.ts index 647af3a52..3145f1bb6 100644 --- a/packages/taco/src/taco.ts +++ b/packages/taco/src/taco.ts @@ -155,7 +155,6 @@ export const decrypt = async ( domain, messageKit.acp.publicKey, ); - const ritual = await DkgClient.getActiveRitual(provider, domain, ritualId); const authProviders: AuthProviders = authProvider ? { [EIP4361_AUTH_METHOD]: authProvider, @@ -167,8 +166,6 @@ export const decrypt = async ( porterUrisFull, messageKit, ritualId, - ritual.sharesNum, - ritual.threshold, authProviders, customParameters, ); @@ -200,6 +197,7 @@ export const isAuthorized = async ( messageKit, ); +// TODO is this still valid and actually needed? should we remove this? export const registerEncrypters = async ( provider: ethers.providers.Provider, signer: ethers.Signer, diff --git a/packages/taco/src/tdec.ts b/packages/taco/src/tdec.ts index 8c7008b71..bf3cd3fdc 100644 --- a/packages/taco/src/tdec.ts +++ b/packages/taco/src/tdec.ts @@ -25,6 +25,7 @@ import { arrayify, keccak256 } from 'ethers/lib/utils'; import { ConditionExpression } from './conditions/condition-expr'; import { ConditionContext, CustomContextParam } from './conditions/context'; +import { DkgClient } from './dkg'; const ERR_DECRYPTION_FAILED = (errors: unknown) => `Threshold of responses not met; TACo decryption failed with errors: ${JSON.stringify( @@ -64,8 +65,6 @@ export const retrieveAndDecrypt = async ( porterUris: string[], thresholdMessageKit: ThresholdMessageKit, ritualId: number, - sharesNum: number, - threshold: number, authProviders?: AuthProviders, customParameters?: Record, ): Promise => { @@ -75,8 +74,6 @@ export const retrieveAndDecrypt = async ( porterUris, thresholdMessageKit, ritualId, - sharesNum, - threshold, authProviders, customParameters, ); @@ -91,16 +88,16 @@ const retrieve = async ( porterUris: string[], thresholdMessageKit: ThresholdMessageKit, ritualId: number, - sharesNum: number, - threshold: number, authProviders?: AuthProviders, customParameters?: Record, ): Promise => { + const ritual = await DkgClient.getActiveRitual(provider, domain, ritualId); + const dkgParticipants = await DkgCoordinatorAgent.getParticipants( provider, domain, ritualId, - sharesNum, + ritual.sharesNum, ); const conditionContext = await ConditionContext.fromConditions( thresholdMessageKit.acp.conditions, @@ -117,9 +114,9 @@ const retrieve = async ( const porter = new PorterClient(porterUris); const { encryptedResponses, errors } = await porter.tacoDecrypt( encryptedRequests, - threshold, + ritual.threshold, ); - if (Object.keys(encryptedResponses).length < threshold) { + if (Object.keys(encryptedResponses).length < ritual.threshold) { throw new Error(ERR_DECRYPTION_FAILED(errors)); } From bb99d4a3cda5cda741b2632d19cd3519e0c9d4f7 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 16:29:53 -0400 Subject: [PATCH 02/21] Allow creation of Context from a message kit. Allow Context to be created with a condition, and subsequently populated with authProviders and customParameters. Make :userAddressExternalEIP4361 a reserved context variable because the context will use the provided authProviders to populate its (and :userAddress) values in the overall context used for decryption. --- packages/taco/src/conditions/const.ts | 2 +- .../taco/src/conditions/context/context.ts | 145 +++++++++--------- 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/packages/taco/src/conditions/const.ts b/packages/taco/src/conditions/const.ts index 1eb809a92..da603bdda 100644 --- a/packages/taco/src/conditions/const.ts +++ b/packages/taco/src/conditions/const.ts @@ -23,6 +23,6 @@ export const USER_ADDRESS_PARAMS = [ ]; export const RESERVED_CONTEXT_PARAMS = [ + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, USER_ADDRESS_PARAM_DEFAULT, - // USER_ADDRESS_PARAM_EXTERNAL_EIP4361 is not reserved and can be used as a custom context parameter ]; diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 015c34afc..60aab9e8d 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -1,9 +1,11 @@ import { ThresholdMessageKit } from '@nucypher/nucypher-core'; import { toJSON } from '@nucypher/shared'; import { - AUTH_METHOD_FOR_PARAM, - AuthProviders, + AuthProvider, AuthSignature, + EIP4361AuthProvider, + SingleSignOnEIP4361AuthProvider, + USER_ADDRESS_PARAM_DEFAULT, } from '@nucypher/taco-auth'; import { CoreConditions, CoreContext } from '../../types'; @@ -14,10 +16,11 @@ import { CONTEXT_PARAM_PREFIX, CONTEXT_PARAM_REGEXP, RESERVED_CONTEXT_PARAMS, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, USER_ADDRESS_PARAMS, } from '../const'; -export type CustomContextParam = string | number | boolean | AuthSignature; +export type CustomContextParam = string | number | boolean; export type ContextParam = CustomContextParam | AuthSignature; const ERR_RESERVED_PARAM = (key: string) => @@ -28,39 +31,26 @@ const ERR_AUTH_PROVIDER_REQUIRED = (key: string) => `No matching authentication provider to satisfy ${key} context variable in condition`; const ERR_MISSING_CONTEXT_PARAMS = (params: string[]) => `Missing custom context parameter(s): ${params.join(', ')}`; -const ERR_UNKNOWN_CONTEXT_PARAMS = (params: string[]) => - `Unknown custom context parameter(s): ${params.join(', ')}`; -const ERR_NO_AUTH_PROVIDER_FOR_PARAM = (param: string) => - `No custom parameter for requested context parameter: ${param}`; +const ERR_UNKNOWN_CUSTOM_CONTEXT_PARAM = (param: string) => + `Unknown custom context parameter: ${param}`; +const ERR_INVALID_AUTH_PROVIDER_TYPE = (param: string, expected: string) => + `Invalid AuthProvider type for ${param}; expected ${expected}`; +const ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM = (param: string) => + `AuthProvider not necessary for context parameter: ${param}`; export class ConditionContext { public requestedParameters: Set; + private customParameters: Record = {}; + private authProviders: Record = {}; - constructor( - condition: Condition, - public readonly customParameters: Record = {}, - private readonly authProviders: AuthProviders = {}, - ) { + constructor(condition: Condition) { const condProps = condition.toObj(); - this.validateContextParameters(); - this.validateCoreConditions(condProps); + ConditionContext.validateCoreConditions(condProps); this.requestedParameters = ConditionContext.findContextParameters(condProps); - this.validateAuthProviders(this.requestedParameters); } - private validateContextParameters(): void { - Object.keys(this.customParameters).forEach((key) => { - if (RESERVED_CONTEXT_PARAMS.includes(key)) { - throw new Error(ERR_RESERVED_PARAM(key)); - } - if (!key.startsWith(CONTEXT_PARAM_PREFIX)) { - throw new Error(ERR_INVALID_CUSTOM_PARAM(key)); - } - }); - } - - private validateCoreConditions(condObject: ConditionProps) { + private static validateCoreConditions(condObject: ConditionProps) { // Checking whether the condition is compatible with the current version of the library // Intentionally ignoring the return value of the function new CoreConditions(toJSON(condObject)); @@ -77,16 +67,6 @@ export class ConditionContext { if (missingParameters.length > 0) { throw new Error(ERR_MISSING_CONTEXT_PARAMS(missingParameters)); } - - // We may also have some parameters that are not used - const unknownParameters = Object.keys(parameters).filter( - (key) => - !this.requestedParameters.has(key) && - !RESERVED_CONTEXT_PARAMS.includes(key), - ); - if (unknownParameters.length > 0) { - throw new Error(ERR_UNKNOWN_CONTEXT_PARAMS(unknownParameters)); - } } private async fillContextParameters( @@ -100,22 +80,15 @@ export class ConditionContext { return parameters; } - private validateAuthProviders(requestedParameters: Set): void { - for (const param of requestedParameters) { + private validateAuthProviders(): void { + for (const param of this.requestedParameters) { // If it's not a user address parameter, we can skip if (!USER_ADDRESS_PARAMS.includes(param)) { continue; } - // If it's a user address parameter, we need to check if we have an auth provider - const authMethod = AUTH_METHOD_FOR_PARAM[param]; - if (!authMethod && !this.customParameters[param]) { - // If we don't have an auth method, and we don't have a custom parameter, we have a problem - throw new Error(ERR_NO_AUTH_PROVIDER_FOR_PARAM(param)); - } - - // If we have an auth method, but we don't have an auth provider, we have a problem - if (authMethod && !this.authProviders[authMethod]) { + // we don't have a corresponding auth provider, we have a problem + if (!this.authProviders[param]) { throw new Error(ERR_AUTH_PROVIDER_REQUIRED(param)); } } @@ -126,10 +99,9 @@ export class ConditionContext { ): Promise> { const entries = await Promise.all( [...requestedParameters] - .map((param) => [param, AUTH_METHOD_FOR_PARAM[param]]) - .filter(([, authMethod]) => !!authMethod) - .map(async ([param, authMethod]) => { - const maybeAuthProvider = this.authProviders[authMethod]; + .filter((param) => USER_ADDRESS_PARAMS.includes(param)) + .map(async (param) => { + const maybeAuthProvider = this.authProviders[param]; // TODO: Throw here instead of validating in the constructor? // TODO: Hide getOrCreateAuthSignature behind a more generic interface return [param, await maybeAuthProvider!.getOrCreateAuthSignature()]; @@ -138,11 +110,25 @@ export class ConditionContext { return Object.fromEntries(entries); } + private validateCustomContextParameter(customParam: string): void { + if (!ConditionContext.isContextParameter(customParam)) { + throw new Error(ERR_INVALID_CUSTOM_PARAM(customParam)); + } + + if (RESERVED_CONTEXT_PARAMS.includes(customParam)) { + throw new Error(ERR_RESERVED_PARAM(customParam)); + } + + if (!this.requestedParameters.has(customParam)) { + throw new Error(ERR_UNKNOWN_CUSTOM_CONTEXT_PARAM(customParam)); + } + } + private static isContextParameter(param: unknown): boolean { return !!String(param).match(CONTEXT_PARAM_REGEXP); } - public static findContextParameters(condition: ConditionProps) { + private static findContextParameters(condition: ConditionProps) { // First, we want to find all the parameters we need to add const requestedParameters = new Set(); @@ -183,6 +169,38 @@ export class ConditionContext { return requestedParameters; } + public addCustomContextParameterValues( + customParameters: Record, + ) { + Object.keys(customParameters).forEach((key) => { + this.validateCustomContextParameter(key); + this.customParameters[key] = customParameters[key]; + }); + } + + public addAuthProvider(contextParam: string, authProvider: AuthProvider) { + if (!USER_ADDRESS_PARAMS.includes(contextParam)) { + throw new Error( + ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM(contextParam), + ); + } + + if (contextParam === USER_ADDRESS_PARAM_DEFAULT) { + if (authProvider instanceof EIP4361AuthProvider) { + this.authProviders[contextParam] = authProvider; + return; + } + } else if (contextParam === USER_ADDRESS_PARAM_EXTERNAL_EIP4361) { + if (authProvider instanceof SingleSignOnEIP4361AuthProvider) { + this.authProviders[contextParam] = authProvider; + return; + } + } + throw new Error( + ERR_INVALID_AUTH_PROVIDER_TYPE(contextParam, typeof authProvider), + ); + } + public async toJson(): Promise { const parameters = await this.toContextParameters(); return toJSON(parameters); @@ -196,6 +214,7 @@ export class ConditionContext { public toContextParameters = async (): Promise< Record > => { + this.validateAuthProviders(); const parameters = await this.fillContextParameters( this.requestedParameters, ); @@ -203,26 +222,12 @@ export class ConditionContext { return parameters; }; - public static fromConditions( - conditions: CoreConditions, - authProviders?: AuthProviders, - customParameters?: Record, - ): ConditionContext { - return new ConditionContext( - ConditionExpression.fromCoreConditions(conditions).condition, - customParameters, - authProviders, - ); - } - - public static requestedContextParameters( + public static fromMessageKit( messageKit: ThresholdMessageKit, - ): Set { + ): ConditionContext { const conditionExpr = ConditionExpression.fromCoreConditions( messageKit.acp.conditions, ); - return ConditionContext.findContextParameters( - conditionExpr.condition.toObj(), - ); + return new ConditionContext(conditionExpr.condition); } } From ee262c44af79e292520cc5af04c58fe07b3a7276 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 16:31:30 -0400 Subject: [PATCH 03/21] Don't have ConditionExpr build a context, the context can be created on its own. --- packages/taco/src/conditions/condition-expr.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/taco/src/conditions/condition-expr.ts b/packages/taco/src/conditions/condition-expr.ts index e62ed2b49..752ad5fcc 100644 --- a/packages/taco/src/conditions/condition-expr.ts +++ b/packages/taco/src/conditions/condition-expr.ts @@ -1,11 +1,9 @@ import { Conditions as CoreConditions } from '@nucypher/nucypher-core'; import { toJSON } from '@nucypher/shared'; -import { AuthProviders } from '@nucypher/taco-auth'; import { SemVer } from 'semver'; import { Condition } from './condition'; import { ConditionFactory } from './condition-factory'; -import { ConditionContext, CustomContextParam } from './context'; const ERR_VERSION = (provided: string, current: string) => `Version provided, ${provided}, is incompatible with current version, ${current}`; @@ -64,17 +62,6 @@ export class ConditionExpression { return ConditionExpression.fromJSON(conditions.toString()); } - public buildContext( - customParameters: Record = {}, - authProviders: AuthProviders = {}, - ): ConditionContext { - return new ConditionContext( - this.condition, - customParameters, - authProviders, - ); - } - public equals(other: ConditionExpression): boolean { return [ this.version === other.version, From 9e443e29297a29a57d09ab84a7b6da7aca15223b Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 16:32:28 -0400 Subject: [PATCH 04/21] Allow ConditionContext to be an optional parameter for decryption; it replaces authProvider and customParmeters since those are now encompassed in the context itself. --- packages/taco/src/taco.ts | 24 +++++------------------- packages/taco/src/tdec.ts | 21 ++++++++------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/packages/taco/src/taco.ts b/packages/taco/src/taco.ts index 3145f1bb6..33d26f302 100644 --- a/packages/taco/src/taco.ts +++ b/packages/taco/src/taco.ts @@ -13,17 +13,12 @@ import { GlobalAllowListAgent, toBytes, } from '@nucypher/shared'; -import { - AuthProviders, - EIP4361_AUTH_METHOD, - EIP4361AuthProvider, -} from '@nucypher/taco-auth'; import { ethers } from 'ethers'; import { keccak256 } from 'ethers/lib/utils'; import { Condition } from './conditions/condition'; import { ConditionExpression } from './conditions/condition-expr'; -import { CustomContextParam } from './conditions/context'; +import { ConditionContext } from './conditions/context'; import { DkgClient } from './dkg'; import { retrieveAndDecrypt } from './tdec'; @@ -129,11 +124,9 @@ export const encryptWithPublicKey = async ( * @param {Domain} domain - Represents the logical network in which the decryption will be performed. * Must match the `ritualId`. * @param {ThresholdMessageKit} messageKit - The kit containing the message to be decrypted - * @param authProvider - The authentication provider that will be used to provide the authorization - * @param {string[]} [porterUris] - The URI(s) for the Porter service. If not provided, a value will be obtained + * @param {ConditionContext} context - Optional context data used for decryption time values for the condition(s) within the `messageKit`. + * @param {string[]} [porterUris] - Optional URI(s) for the Porter service. If not provided, a value will be obtained * from the Domain - * @param {Record} [customParameters] - Optional custom parameters that may be required - * depending on the condition used * * @returns {Promise} Returns Promise that resolves with a decrypted message * @@ -144,9 +137,8 @@ export const decrypt = async ( provider: ethers.providers.Provider, domain: Domain, messageKit: ThresholdMessageKit, - authProvider?: EIP4361AuthProvider, + context?: ConditionContext, porterUris?: string[], - customParameters?: Record, ): Promise => { const porterUrisFull: string[] = porterUris ? porterUris : await getPorterUris(domain); @@ -155,19 +147,13 @@ export const decrypt = async ( domain, messageKit.acp.publicKey, ); - const authProviders: AuthProviders = authProvider - ? { - [EIP4361_AUTH_METHOD]: authProvider, - } - : {}; return retrieveAndDecrypt( provider, domain, porterUrisFull, messageKit, ritualId, - authProviders, - customParameters, + context, ); }; diff --git a/packages/taco/src/tdec.ts b/packages/taco/src/tdec.ts index bf3cd3fdc..7b33f9ccc 100644 --- a/packages/taco/src/tdec.ts +++ b/packages/taco/src/tdec.ts @@ -19,12 +19,11 @@ import { PorterClient, toBytes, } from '@nucypher/shared'; -import { AuthProviders } from '@nucypher/taco-auth'; import { ethers } from 'ethers'; import { arrayify, keccak256 } from 'ethers/lib/utils'; import { ConditionExpression } from './conditions/condition-expr'; -import { ConditionContext, CustomContextParam } from './conditions/context'; +import { ConditionContext } from './conditions/context'; import { DkgClient } from './dkg'; const ERR_DECRYPTION_FAILED = (errors: unknown) => @@ -65,8 +64,7 @@ export const retrieveAndDecrypt = async ( porterUris: string[], thresholdMessageKit: ThresholdMessageKit, ritualId: number, - authProviders?: AuthProviders, - customParameters?: Record, + context?: ConditionContext, ): Promise => { const decryptionShares = await retrieve( provider, @@ -74,8 +72,7 @@ export const retrieveAndDecrypt = async ( porterUris, thresholdMessageKit, ritualId, - authProviders, - customParameters, + context, ); const sharedSecret = combineDecryptionSharesSimple(decryptionShares); return thresholdMessageKit.decryptWithSharedSecret(sharedSecret); @@ -88,8 +85,7 @@ const retrieve = async ( porterUris: string[], thresholdMessageKit: ThresholdMessageKit, ritualId: number, - authProviders?: AuthProviders, - customParameters?: Record, + context?: ConditionContext, ): Promise => { const ritual = await DkgClient.getActiveRitual(provider, domain, ritualId); @@ -99,11 +95,10 @@ const retrieve = async ( ritualId, ritual.sharesNum, ); - const conditionContext = await ConditionContext.fromConditions( - thresholdMessageKit.acp.conditions, - authProviders, - customParameters, - ); + const conditionContext = context + ? context + : ConditionContext.fromMessageKit(thresholdMessageKit); + const { sharedSecrets, encryptedRequests } = await makeDecryptionRequests( ritualId, conditionContext, From f2c83585c4f6ad34488440339fd663135c6034ca Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 16:33:16 -0400 Subject: [PATCH 05/21] Allow address property to be public for SingleSignOnExternalEIP4361AuthProvider. --- packages/taco-auth/src/providers/external-eip4361.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/taco-auth/src/providers/external-eip4361.ts b/packages/taco-auth/src/providers/external-eip4361.ts index b2c550b86..9ebd60892 100644 --- a/packages/taco-auth/src/providers/external-eip4361.ts +++ b/packages/taco-auth/src/providers/external-eip4361.ts @@ -22,7 +22,7 @@ export class SingleSignOnEIP4361AuthProvider { private constructor( private readonly existingSiweMessage: string, - private readonly address: string, + public readonly address: string, private readonly signature: string, ) {} From 68788f6c7a688be73ca7c0d5482cdb837ff57de3 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 16:33:49 -0400 Subject: [PATCH 06/21] Update tests to accomodate new architecture for ConditionContext. --- .../test/conditions/base/contract.test.ts | 25 +- .../taco/test/conditions/conditions.test.ts | 13 +- packages/taco/test/conditions/context.test.ts | 219 ++++++++++-------- packages/taco/test/taco.test.ts | 11 +- packages/test-utils/src/utils.ts | 36 ++- 5 files changed, 183 insertions(+), 121 deletions(-) diff --git a/packages/taco/test/conditions/base/contract.test.ts b/packages/taco/test/conditions/base/contract.test.ts index 849ae2b5f..67e3ec26f 100644 --- a/packages/taco/test/conditions/base/contract.test.ts +++ b/packages/taco/test/conditions/base/contract.test.ts @@ -1,5 +1,5 @@ import { initialize } from '@nucypher/nucypher-core'; -import { USER_ADDRESS_PARAM_DEFAULT } from '@nucypher/taco-auth'; +import { AuthProvider, USER_ADDRESS_PARAM_DEFAULT } from '@nucypher/taco-auth'; import { fakeAuthProviders } from '@nucypher/test-utils'; import { beforeAll, describe, expect, it } from 'vitest'; @@ -10,9 +10,11 @@ import { ContractConditionType, FunctionAbiProps, } from '../../../src/conditions/base/contract'; -import { ConditionExpression } from '../../../src/conditions/condition-expr'; import { USER_ADDRESS_PARAMS } from '../../../src/conditions/const'; -import { CustomContextParam } from '../../../src/conditions/context'; +import { + ConditionContext, + CustomContextParam, +} from '../../../src/conditions/context'; import { testContractConditionObj, testFunctionAbi } from '../../test-utils'; describe('validation', () => { @@ -170,7 +172,7 @@ describe('supports various user address params', () => { ); }); -describe('supports custom function abi', () => { +describe('supports custom function abi', async () => { const contractConditionObj: ContractConditionProps = { ...testContractConditionObj, standardContractType: undefined, @@ -183,19 +185,26 @@ describe('supports custom function abi', () => { }, }; const contractCondition = new ContractCondition(contractConditionObj); - const conditionExpr = new ConditionExpression(contractCondition); const myCustomParam = ':customParam'; const customParams: Record = {}; customParams[myCustomParam] = 1234; + let authProviders: Record; beforeAll(async () => { await initialize(); + authProviders = await fakeAuthProviders(); }); it('accepts custom function abi with a custom parameter', async () => { - const asJson = await conditionExpr - .buildContext(customParams, fakeAuthProviders()) - .toJson(); + const conditionContext = new ConditionContext(contractCondition); + conditionContext.addCustomContextParameterValues(customParams); + + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); + + const asJson = await conditionContext.toJson(); expect(asJson).toBeDefined(); expect(asJson).toContain(USER_ADDRESS_PARAM_DEFAULT); expect(asJson).toContain(myCustomParam); diff --git a/packages/taco/test/conditions/conditions.test.ts b/packages/taco/test/conditions/conditions.test.ts index ad5d735b2..3be3f664a 100644 --- a/packages/taco/test/conditions/conditions.test.ts +++ b/packages/taco/test/conditions/conditions.test.ts @@ -1,4 +1,5 @@ import { ChainId } from '@nucypher/shared'; +import { AuthProvider, USER_ADDRESS_PARAM_DEFAULT } from '@nucypher/taco-auth'; import { fakeAuthProviders } from '@nucypher/test-utils'; import { beforeAll, describe, expect, it } from 'vitest'; @@ -8,8 +9,10 @@ import { SUPPORTED_CHAIN_IDS } from '../../src/conditions/const'; import { ConditionContext } from '../../src/conditions/context'; describe('conditions', () => { + let authProviders: Record; beforeAll(async () => { await initialize(); + authProviders = await fakeAuthProviders(); }); it('creates a complex condition with custom parameters', async () => { @@ -37,11 +40,13 @@ describe('conditions', () => { expect(condition).toBeDefined(); expect(condition.requiresAuthentication()).toBeTruthy(); - const context = new ConditionContext( - condition, - { ':time': 100 }, - fakeAuthProviders(), + const context = new ConditionContext(condition); + context.addCustomContextParameterValues({ ':time': 100 }); + context.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], ); + expect(context).toBeDefined(); const asObj = await context.toContextParameters(); diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index fb8bce5b9..f007ae926 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -1,9 +1,9 @@ import { initialize } from '@nucypher/nucypher-core'; import { - AuthProviders, + AuthProvider, AuthSignature, - EIP4361_AUTH_METHOD, EIP4361AuthProvider, + SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, } from '@nucypher/taco-auth'; import { @@ -20,12 +20,14 @@ import { ContractConditionProps, } from '../../src/conditions/base/contract'; import { RpcCondition } from '../../src/conditions/base/rpc'; -import { ConditionExpression } from '../../src/conditions/condition-expr'; import { RESERVED_CONTEXT_PARAMS, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '../../src/conditions/const'; -import { CustomContextParam } from '../../src/conditions/context'; +import { + ConditionContext, + CustomContextParam, +} from '../../src/conditions/context'; import { paramOrContextParamSchema, ReturnValueTestProps, @@ -38,11 +40,10 @@ import { } from '../test-utils'; describe('context', () => { - let authProviders: AuthProviders; - + let authProviders: Record; beforeAll(async () => { await initialize(); - authProviders = fakeAuthProviders(); + authProviders = await fakeAuthProviders(); }); describe('serialization', () => { @@ -55,9 +56,11 @@ describe('context', () => { value: USER_ADDRESS_PARAM_DEFAULT, }, }); - const conditionContext = new ConditionExpression( - rpcCondition, - ).buildContext({}, authProviders); + const conditionContext = new ConditionContext(rpcCondition); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); const asJson = await conditionContext.toJson(); expect(asJson).toBeDefined(); @@ -78,69 +81,74 @@ describe('context', () => { }, }; const contractCondition = new ContractCondition(contractConditionObj); - const conditionExpr = new ConditionExpression(contractCondition); - describe('custom parameters', () => { + it('detects when a custom parameter is requested', () => { + const conditionContext = new ConditionContext(contractCondition); + expect(conditionContext.requestedParameters).toContain(customParamKey); + }); + it('serializes bytes as hex strings', async () => { const customParamsWithBytes: Record = {}; const customParam = toBytes('hello'); // Uint8Array is not a valid CustomContextParam, override the type: customParamsWithBytes[customParamKey] = customParam as unknown as string; - const contextAsJson = await conditionExpr - .buildContext(customParamsWithBytes) - .toJson(); + + const conditionContext = new ConditionContext(contractCondition); + conditionContext.addCustomContextParameterValues(customParamsWithBytes); + const contextAsJson = await conditionContext.toJson(); const asObj = JSON.parse(contextAsJson); expect(asObj).toBeDefined(); expect(asObj[customParamKey]).toEqual(`0x${toHexString(customParam)}`); }); - - it('detects when a custom parameter is requested', () => { - const context = conditionExpr.buildContext({}, authProviders); - expect(context.requestedParameters).toContain(customParamKey); - }); }); describe('return value test', () => { - it('accepts on a custom context parameters', async () => { - const asObj = await conditionExpr - .buildContext(customParams) - .toContextParameters(); + it('accepts only custom context parameters', async () => { + const conditionContext = new ConditionContext(contractCondition); + conditionContext.addCustomContextParameterValues(customParams); + const asObj = await conditionContext.toContextParameters(); expect(asObj).toBeDefined(); expect(asObj[customParamKey]).toEqual(1234); }); it('rejects on a missing custom context parameter', async () => { - const context = conditionExpr.buildContext({}, authProviders); - await expect(context.toContextParameters()).rejects.toThrow( + const conditionContext = new ConditionContext(contractCondition); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); + await expect(conditionContext.toContextParameters()).rejects.toThrow( `Missing custom context parameter(s): ${customParamKey}`, ); }); }); it('rejects on using reserved context parameter', () => { + const conditionContext = new ConditionContext(contractCondition); RESERVED_CONTEXT_PARAMS.forEach((reservedParam) => { const badCustomParams: Record = {}; badCustomParams[reservedParam] = 'this-will-throw'; - expect(() => conditionExpr.buildContext(badCustomParams)).toThrow( + expect(() => + conditionContext.addCustomContextParameterValues(badCustomParams), + ).toThrow( `Cannot use reserved parameter name ${reservedParam} as custom parameter`, ); }); }); - it('rejects on using a custom parameter that was not requested', async () => { + it('rejects on using a custom parameter that was not requested', () => { const badCustomParamKey = ':notRequested'; const badCustomParams: Record = {}; badCustomParams[customParamKey] = 'this-is-fine'; badCustomParams[badCustomParamKey] = 'this-will-throw'; - await expect( - conditionExpr.buildContext(badCustomParams).toContextParameters(), - ).rejects.toThrow( - `Unknown custom context parameter(s): ${badCustomParamKey}`, - ); + const conditionContext = new ConditionContext(contractCondition); + expect(() => + conditionContext.addCustomContextParameterValues(badCustomParams), + ).toThrow(`Unknown custom context parameter: ${badCustomParamKey}`); }); - it('detects when auth provider is required by parameters', () => { + it('detects when auth provider is required by parameters', async () => { const conditionObj = { ...testContractConditionObj, parameters: [USER_ADDRESS_PARAM_DEFAULT], @@ -150,14 +158,13 @@ describe('context', () => { } as ReturnValueTestProps, }; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); - expect(conditionExpr.buildContext({}, authProviders)).toBeDefined(); - expect(() => conditionExpr.buildContext({})).toThrow( + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_DEFAULT} context variable in condition`, ); }); - it('detects when signer is required by return value test', () => { + it('detects when signer is required by return value test', async () => { const conditionObj = { ...testContractConditionObj, standardContractType: 'ERC721', @@ -169,24 +176,22 @@ describe('context', () => { }, } as ContractConditionProps; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); - expect(conditionExpr.buildContext({}, authProviders)).toBeDefined(); - expect(() => conditionExpr.buildContext({})).toThrow( + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_DEFAULT} context variable in condition`, ); }); - it('detects when signer is not required', () => { + it('detects when signer is not required', async () => { const condition = new RpcCondition(testRpcConditionObj); - const conditionExpr = new ConditionExpression(condition); + const conditionContext = new ConditionContext(condition); expect( JSON.stringify(condition.toObj()).includes(USER_ADDRESS_PARAM_DEFAULT), ).toBe(false); - expect(conditionExpr.buildContext({}, authProviders)).toBeDefined(); - expect(conditionExpr.buildContext({})).toBeDefined(); + await expect(conditionContext.toContextParameters()).toBeDefined(); }); - it('rejects on a missing signer', () => { + it('rejects on a missing signer', async () => { const conditionObj = { ...testContractConditionObj, returnValueTest: { @@ -195,24 +200,24 @@ describe('context', () => { }, }; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); - expect(() => conditionExpr.buildContext({}, undefined)).toThrow( + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_DEFAULT} context variable in condition`, ); }); - it('rejects on a missing signer', () => { + it('rejects on a missing signer for single sign-on EIP4361', async () => { const conditionObj = { ...testContractConditionObj, returnValueTest: { ...testReturnValueTest, - value: USER_ADDRESS_PARAM_DEFAULT, + value: USER_ADDRESS_PARAM_EXTERNAL_EIP4361, }, }; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); - expect(() => conditionExpr.buildContext({}, undefined)).toThrow( - `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_DEFAULT} context variable in condition`, + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( + `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361} context variable in condition`, ); }); @@ -229,21 +234,22 @@ describe('context', () => { }; it('handles both custom and auth context parameters', () => { - const requestedParams = new ConditionExpression( - contractCondition, - ).buildContext({}, authProviders).requestedParameters; + const requestedParams = new ConditionContext(contractCondition) + .requestedParameters; expect(requestedParams).not.toContain(USER_ADDRESS_PARAM_DEFAULT); expect(requestedParams).toContain(customParamKey); }); - it('rejects on a missing parameter ', async () => { + it('rejects on a missing custom parameter ', async () => { const customContractCondition = new ContractCondition({ ...contractConditionObj, parameters: [USER_ADDRESS_PARAM_DEFAULT, customParamKey], }); - const conditionContext = new ConditionExpression( - customContractCondition, - ).buildContext({}, authProviders); + const conditionContext = new ConditionContext(customContractCondition); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); await expect(async () => conditionContext.toContextParameters(), @@ -257,9 +263,11 @@ describe('context', () => { ...contractConditionObj, parameters: [USER_ADDRESS_PARAM_DEFAULT, 100], }); - const conditionContext = new ConditionExpression( - customContractCondition, - ).buildContext({}, authProviders); + const conditionContext = new ConditionContext(customContractCondition); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); const asObj = await conditionContext.toContextParameters(); expect(asObj).toBeDefined(); @@ -275,9 +283,15 @@ describe('context', () => { }); const customParameters: Record = {}; customParameters[customParamKey] = falsyParam; - const conditionContext = new ConditionExpression( + + const conditionContext = new ConditionContext( customContractCondition, - ).buildContext(customParameters, authProviders); + ); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); + conditionContext.addCustomContextParameterValues(customParameters); const asObj = await conditionContext.toContextParameters(); expect(asObj).toBeDefined(); @@ -294,18 +308,23 @@ describe('context', () => { describe('No authentication provider', () => { let provider: ethers.providers.Provider; let signer: ethers.Signer; - let authProviders: AuthProviders; + let authProviders: Record; - async function testEIP4361AuthSignature(authSignature: AuthSignature) { + async function testEIP4361AuthSignature( + authSignature: AuthSignature, + expectedAddress?: string, + ) { expect(authSignature).toBeDefined(); expect(authSignature.signature).toBeDefined(); expect(authSignature.scheme).toEqual('EIP4361'); - const signerAddress = await signer.getAddress(); - expect(authSignature.address).toEqual(signerAddress); + const addressToUse = expectedAddress + ? expectedAddress + : await signer.getAddress(); + expect(authSignature.address).toEqual(addressToUse); expect(authSignature.typedData).toContain( - `localhost wants you to sign in with your Ethereum account:\n${signerAddress}`, + `localhost wants you to sign in with your Ethereum account:\n${addressToUse}`, ); expect(authSignature.typedData).toContain('URI: http://localhost:3000'); @@ -317,11 +336,11 @@ describe('No authentication provider', () => { await initialize(); provider = fakeProvider(); signer = fakeSigner(); - authProviders = fakeAuthProviders(); + authProviders = await fakeAuthProviders(); }); it('throws an error if there is no auth provider', () => { - RESERVED_CONTEXT_PARAMS.forEach((userAddressParam) => { + RESERVED_CONTEXT_PARAMS.forEach(async (userAddressParam) => { const conditionObj = { ...testContractConditionObj, returnValueTest: { @@ -330,14 +349,14 @@ describe('No authentication provider', () => { }, }; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); - expect(() => conditionExpr.buildContext({}, {})).toThrow( + const conditionContext = new ConditionContext(condition); + await expect(conditionContext.toContextParameters()).rejects.toThrow( `No matching authentication provider to satisfy ${userAddressParam} context variable in condition`, ); }); }); - it('it supports just one provider at a time', () => { + it('it supports just one provider at a time', async () => { const conditionObj = { ...testContractConditionObj, returnValueTest: { @@ -346,8 +365,12 @@ describe('No authentication provider', () => { }, }; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); - expect(() => conditionExpr.buildContext({}, authProviders)).not.toThrow(); + const conditionContext = new ConditionContext(condition); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); + expect(async () => conditionContext.toContextParameters()).not.toThrow(); }); async function makeAuthSignature(authMethod: string) { @@ -359,10 +382,13 @@ describe('No authentication provider', () => { }, }; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); - const builtContext = conditionExpr.buildContext({}, authProviders); - const contextVars = await builtContext.toContextParameters(); + const conditionContext = new ConditionContext(condition); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ); + const contextVars = await conditionContext.toContextParameters(); const authSignature = contextVars[authMethod] as AuthSignature; expect(authSignature).toBeDefined(); @@ -384,12 +410,6 @@ describe('No authentication provider', () => { }); it('supports reusing external eip4361', async () => { - // Because we are reusing an existing SIWE auth message, we have to pass it as a custom parameter - const authMessage = await makeAuthSignature(USER_ADDRESS_PARAM_DEFAULT); - const customParams: Record = { - [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: authMessage as CustomContextParam, - }; - // Spying on the EIP4361 provider to make sure it's not called const eip4361Spy = vi.spyOn( EIP4361AuthProvider.prototype, @@ -405,21 +425,19 @@ describe('No authentication provider', () => { }, }; const condition = new ContractCondition(conditionObj); - const conditionExpr = new ConditionExpression(condition); + const conditionContext = new ConditionContext(condition); - // Make sure we remove the EIP4361 auth method from the auth providers first - delete authProviders[EIP4361_AUTH_METHOD]; // Should throw an error if we don't pass the custom parameter - expect(() => conditionExpr.buildContext({}, authProviders)).toThrow( - `No custom parameter for requested context parameter: ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361}`, + await expect(conditionContext.toContextParameters()).rejects.toThrow( + `No matching authentication provider to satisfy ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361} context variable in condition`, ); - // Remembering to pass in customParams here: - const builtContext = conditionExpr.buildContext( - customParams, - authProviders, + // Remembering to pass in auth provider + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], ); - const contextVars = await builtContext.toContextParameters(); + const contextVars = await conditionContext.toContextParameters(); expect(eip4361Spy).not.toHaveBeenCalled(); // Now, we expect that the auth signature will be available in the context variables @@ -427,7 +445,14 @@ describe('No authentication provider', () => { USER_ADDRESS_PARAM_EXTERNAL_EIP4361 ] as AuthSignature; expect(authSignature).toBeDefined(); - await testEIP4361AuthSignature(authSignature); + await testEIP4361AuthSignature( + authSignature, + ( + authProviders[ + USER_ADDRESS_PARAM_EXTERNAL_EIP4361 + ] as SingleSignOnEIP4361AuthProvider + ).address, + ); }); }); diff --git a/packages/taco/test/taco.test.ts b/packages/taco/test/taco.test.ts index 8fd2545a0..cbb0a91da 100644 --- a/packages/taco/test/taco.test.ts +++ b/packages/taco/test/taco.test.ts @@ -21,6 +21,7 @@ import { beforeAll, describe, expect, it } from 'vitest'; import * as taco from '../src'; import { conditions, domains, toBytes } from '../src'; +import { ConditionContext } from '../src/conditions/context'; import { fakeDkgRitual, @@ -88,11 +89,13 @@ describe('taco', () => { signer, TEST_SIWE_PARAMS, ); + const conditionContext = ConditionContext.fromMessageKit(messageKit); + conditionContext.addAuthProvider(USER_ADDRESS_PARAM_DEFAULT, authProvider); const decryptedMessage = await taco.decrypt( provider, domains.DEVNET, messageKit, - authProvider, + conditionContext, [fakePorterUri], ); expect(decryptedMessage).toEqual(toBytes(message)); @@ -128,10 +131,8 @@ describe('taco', () => { ); expect(getFinalizedRitualSpy).toHaveBeenCalled(); - const requestedParameters = - taco.conditions.context.ConditionContext.requestedContextParameters( - messageKit, - ); + const conditionContext = ConditionContext.fromMessageKit(messageKit); + const requestedParameters = conditionContext.requestedParameters; expect(requestedParameters).toEqual( new Set([customParamKey, USER_ADDRESS_PARAM_DEFAULT]), ); diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index 07ed9dc1c..b205c2c28 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -37,7 +37,11 @@ import { Ursula, zip, } from '@nucypher/shared'; -import { EIP4361_AUTH_METHOD, EIP4361AuthProvider } from '@nucypher/taco-auth'; +import { + EIP4361AuthProvider, + SingleSignOnEIP4361AuthProvider, + USER_ADDRESS_PARAM_DEFAULT, +} from '@nucypher/taco-auth'; import { ethers, providers, Wallet } from 'ethers'; import { expect, SpyInstance, vi } from 'vitest'; @@ -83,13 +87,11 @@ export const fakeSigner = ( } as unknown as ethers.providers.JsonRpcSigner; }; -export const fakeAuthProviders = () => { +export const fakeAuthProviders = async () => { return { - [EIP4361_AUTH_METHOD]: new EIP4361AuthProvider( - fakeProvider(), - fakeSigner(), - TEST_SIWE_PARAMS, - ), + [USER_ADDRESS_PARAM_DEFAULT]: fakeEIP4351AuthProvider(), + [':userAddressExternalEIP4361']: + await fakeSingleSignOnEIP4361AuthProvider(), }; }; @@ -111,6 +113,26 @@ export const fakeProvider = ( } as unknown as ethers.providers.Web3Provider; }; +const fakeEIP4351AuthProvider = () => { + return new EIP4361AuthProvider( + fakeProvider(), + fakeSigner(), + TEST_SIWE_PARAMS, + ); +}; + +const fakeSingleSignOnEIP4361AuthProvider = async () => { + const message = + 'localhost wants you to sign in with your Ethereum account:\n0x924c255297BF9032583dF6E06a8633dc720aB52D\n\nSign-In With Ethereum Example Statement\n\nURI: http://localhost:3000\nVersion: 1\nChain ID: 1234\nNonce: bTyXgcQxn2htgkjJn\nIssued At: 2024-07-18T16:53:39.093516Z'; + const signature = + '0x22cc163b9c37cf425997b76ebafd44a0d68043d0dc9a1dbf823e78c320924476644f28abcf0974d54b8604eff8a62a51559994537d4b8a85cdee977e02ee98921b'; + + return SingleSignOnEIP4361AuthProvider.fromExistingSiweInfo( + message, + signature, + ); +}; + const genChecksumAddress = (i: number): ChecksumAddress => `0x${'0'.repeat(40 - i.toString(16).length)}${i.toString( 16, From 53a83d29e3d24dcdd378e33a23d02bd9ac14744c Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 19:17:45 -0400 Subject: [PATCH 07/21] Reconfigure taco-auth providers folder to have an eip4361 folder and common elements for both ei4361 provider and single sign-on provider. Move user address context variable for external eip4361 to taco-auth as well. Update relevant imports. --- packages/taco-auth/src/auth-provider.ts | 15 --------------- packages/taco-auth/src/auth-sig.ts | 6 ++++-- .../taco-auth/src/providers/eip4361/common.ts | 17 +++++++++++++++++ .../src/providers/{ => eip4361}/eip4361.ts | 19 ++++--------------- .../{ => eip4361}/external-eip4361.ts | 8 ++++++-- packages/taco-auth/src/providers/index.ts | 4 ++-- packages/taco-auth/test/auth-provider.test.ts | 3 ++- packages/taco/src/conditions/const.ts | 8 ++++---- .../taco/src/conditions/context/context.ts | 1 + packages/taco/src/conditions/shared.ts | 12 +++++------- packages/taco/test/conditions/context.test.ts | 1 + 11 files changed, 46 insertions(+), 48 deletions(-) create mode 100644 packages/taco-auth/src/providers/eip4361/common.ts rename packages/taco-auth/src/providers/{ => eip4361}/eip4361.ts (85%) rename packages/taco-auth/src/providers/{ => eip4361}/external-eip4361.ts (84%) diff --git a/packages/taco-auth/src/auth-provider.ts b/packages/taco-auth/src/auth-provider.ts index fbb8d4c64..cc91f1765 100644 --- a/packages/taco-auth/src/auth-provider.ts +++ b/packages/taco-auth/src/auth-provider.ts @@ -1,20 +1,5 @@ import { AuthSignature } from './auth-sig'; -import { EIP4361AuthProvider } from './providers'; - -export const EIP4361_AUTH_METHOD = 'EIP4361'; export interface AuthProvider { getOrCreateAuthSignature(): Promise; } - -export type AuthProviders = { - [EIP4361_AUTH_METHOD]?: EIP4361AuthProvider; - // Fallback to satisfy type checking - [key: string]: AuthProvider | undefined; -}; - -export const USER_ADDRESS_PARAM_DEFAULT = ':userAddress'; - -export const AUTH_METHOD_FOR_PARAM: Record = { - [USER_ADDRESS_PARAM_DEFAULT]: EIP4361_AUTH_METHOD, -}; diff --git a/packages/taco-auth/src/auth-sig.ts b/packages/taco-auth/src/auth-sig.ts index 01e04ada6..03727842c 100644 --- a/packages/taco-auth/src/auth-sig.ts +++ b/packages/taco-auth/src/auth-sig.ts @@ -1,8 +1,10 @@ import { EthAddressSchema } from '@nucypher/shared'; import { z } from 'zod'; -import { EIP4361_AUTH_METHOD } from './auth-provider'; -import { EIP4361TypedDataSchema } from './providers'; +import { + EIP4361_AUTH_METHOD, + EIP4361TypedDataSchema, +} from './providers/eip4361/common'; export const authSignatureSchema = z.object({ signature: z.string(), diff --git a/packages/taco-auth/src/providers/eip4361/common.ts b/packages/taco-auth/src/providers/eip4361/common.ts new file mode 100644 index 000000000..e6cff1600 --- /dev/null +++ b/packages/taco-auth/src/providers/eip4361/common.ts @@ -0,0 +1,17 @@ +import { SiweMessage } from 'siwe'; +import { z } from 'zod'; + +export const EIP4361_AUTH_METHOD = 'EIP4361'; + +const isSiweMessage = (message: string): boolean => { + try { + new SiweMessage(message); + return true; + } catch { + return false; + } +}; + +export const EIP4361TypedDataSchema = z + .string() + .refine(isSiweMessage, { message: 'Invalid SIWE message' }); diff --git a/packages/taco-auth/src/providers/eip4361.ts b/packages/taco-auth/src/providers/eip4361/eip4361.ts similarity index 85% rename from packages/taco-auth/src/providers/eip4361.ts rename to packages/taco-auth/src/providers/eip4361/eip4361.ts index 8c9a475c6..e5043023a 100644 --- a/packages/taco-auth/src/providers/eip4361.ts +++ b/packages/taco-auth/src/providers/eip4361/eip4361.ts @@ -1,23 +1,12 @@ import { ethers } from 'ethers'; import { generateNonce, SiweMessage } from 'siwe'; -import { z } from 'zod'; -import { EIP4361_AUTH_METHOD } from '../auth-provider'; -import { AuthSignature } from '../auth-sig'; -import { LocalStorage } from '../storage'; +import { AuthSignature } from '../../auth-sig'; +import { LocalStorage } from '../../storage'; -const isSiweMessage = (message: string): boolean => { - try { - new SiweMessage(message); - return true; - } catch { - return false; - } -}; +import { EIP4361_AUTH_METHOD } from './common'; -export const EIP4361TypedDataSchema = z - .string() - .refine(isSiweMessage, { message: 'Invalid SIWE message' }); +export const USER_ADDRESS_PARAM_DEFAULT = ':userAddress'; export type EIP4361AuthProviderParams = { domain: string; diff --git a/packages/taco-auth/src/providers/external-eip4361.ts b/packages/taco-auth/src/providers/eip4361/external-eip4361.ts similarity index 84% rename from packages/taco-auth/src/providers/external-eip4361.ts rename to packages/taco-auth/src/providers/eip4361/external-eip4361.ts index 9ebd60892..20885224a 100644 --- a/packages/taco-auth/src/providers/external-eip4361.ts +++ b/packages/taco-auth/src/providers/eip4361/external-eip4361.ts @@ -1,7 +1,11 @@ import { SiweMessage } from 'siwe'; -import { EIP4361_AUTH_METHOD } from '../auth-provider'; -import { AuthSignature } from '../auth-sig'; +import { AuthSignature } from '../../auth-sig'; + +import { EIP4361_AUTH_METHOD } from './common'; + +export const USER_ADDRESS_PARAM_EXTERNAL_EIP4361 = + ':userAddressExternalEIP4361'; export class SingleSignOnEIP4361AuthProvider { public static async fromExistingSiweInfo( diff --git a/packages/taco-auth/src/providers/index.ts b/packages/taco-auth/src/providers/index.ts index 14f5a1f3f..82912650d 100644 --- a/packages/taco-auth/src/providers/index.ts +++ b/packages/taco-auth/src/providers/index.ts @@ -1,2 +1,2 @@ -export * from './eip4361'; -export * from './external-eip4361'; +export * from './eip4361/eip4361'; +export * from './eip4361/external-eip4361'; diff --git a/packages/taco-auth/test/auth-provider.test.ts b/packages/taco-auth/test/auth-provider.test.ts index a457370d9..d2b11a349 100644 --- a/packages/taco-auth/test/auth-provider.test.ts +++ b/packages/taco-auth/test/auth-provider.test.ts @@ -7,7 +7,8 @@ import { import { SiweMessage } from 'siwe'; import { describe, expect, it } from 'vitest'; -import { EIP4361AuthProvider, EIP4361TypedDataSchema } from '../src'; +import { EIP4361TypedDataSchema } from '../src/providers/eip4361/common'; +import { EIP4361AuthProvider } from '../src/providers'; describe('auth provider', () => { const provider = fakeProvider(bobSecretKeyBytes); diff --git a/packages/taco/src/conditions/const.ts b/packages/taco/src/conditions/const.ts index da603bdda..4d7d06f43 100644 --- a/packages/taco/src/conditions/const.ts +++ b/packages/taco/src/conditions/const.ts @@ -1,8 +1,8 @@ import { ChainId } from '@nucypher/shared'; -import { USER_ADDRESS_PARAM_DEFAULT } from '@nucypher/taco-auth'; - -export const USER_ADDRESS_PARAM_EXTERNAL_EIP4361 = - ':userAddressExternalEIP4361'; +import { + USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, +} from '@nucypher/taco-auth'; // Only allow alphanumeric characters and underscores export const CONTEXT_PARAM_REGEXP = new RegExp('^:[a-zA-Z_][a-zA-Z0-9_]*$'); diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 60aab9e8d..954ca45aa 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -6,6 +6,7 @@ import { EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { CoreConditions, CoreContext } from '../../types'; diff --git a/packages/taco/src/conditions/shared.ts b/packages/taco/src/conditions/shared.ts index 87ea85f7c..504cb2357 100644 --- a/packages/taco/src/conditions/shared.ts +++ b/packages/taco/src/conditions/shared.ts @@ -1,13 +1,11 @@ import { EthAddressSchema } from '@nucypher/shared'; -import { USER_ADDRESS_PARAM_DEFAULT } from '@nucypher/taco-auth'; -import { z } from 'zod'; - import { - CONTEXT_PARAM_PREFIX, - CONTEXT_PARAM_REGEXP, - // TODO consider moving this + USER_ADDRESS_PARAM_DEFAULT, USER_ADDRESS_PARAM_EXTERNAL_EIP4361, -} from './const'; +} from '@nucypher/taco-auth'; +import { z } from 'zod'; + +import { CONTEXT_PARAM_PREFIX, CONTEXT_PARAM_REGEXP } from './const'; export const contextParamSchema = z.string().regex(CONTEXT_PARAM_REGEXP); // We want to discriminate between ContextParams and plain strings diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index f007ae926..1f0b0795a 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -5,6 +5,7 @@ import { EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; import { fakeAuthProviders, From 9c5548cb7c29ff2d9676085ad89ae52dd74f533b Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 19:20:54 -0400 Subject: [PATCH 08/21] Move RESERVED_CONTEXT_VARIABLES constant to context module since it is the only thing using it. --- packages/taco/src/conditions/const.ts | 5 ----- packages/taco/src/conditions/context/context.ts | 7 +++++-- packages/taco/test/conditions/context.test.ts | 5 +---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/taco/src/conditions/const.ts b/packages/taco/src/conditions/const.ts index 4d7d06f43..4cb67d6c2 100644 --- a/packages/taco/src/conditions/const.ts +++ b/packages/taco/src/conditions/const.ts @@ -21,8 +21,3 @@ export const USER_ADDRESS_PARAMS = [ // Ordering matters, this should always be last USER_ADDRESS_PARAM_DEFAULT, ]; - -export const RESERVED_CONTEXT_PARAMS = [ - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, - USER_ADDRESS_PARAM_DEFAULT, -]; diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 954ca45aa..3bc626005 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -16,8 +16,6 @@ import { ConditionExpression } from '../condition-expr'; import { CONTEXT_PARAM_PREFIX, CONTEXT_PARAM_REGEXP, - RESERVED_CONTEXT_PARAMS, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, USER_ADDRESS_PARAMS, } from '../const'; @@ -39,6 +37,11 @@ const ERR_INVALID_AUTH_PROVIDER_TYPE = (param: string, expected: string) => const ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM = (param: string) => `AuthProvider not necessary for context parameter: ${param}`; +export const RESERVED_CONTEXT_PARAMS = [ + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + USER_ADDRESS_PARAM_DEFAULT, +]; + export class ConditionContext { public requestedParameters: Set; private customParameters: Record = {}; diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index 1f0b0795a..796590cb3 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -21,14 +21,11 @@ import { ContractConditionProps, } from '../../src/conditions/base/contract'; import { RpcCondition } from '../../src/conditions/base/rpc'; -import { - RESERVED_CONTEXT_PARAMS, - USER_ADDRESS_PARAM_EXTERNAL_EIP4361, -} from '../../src/conditions/const'; import { ConditionContext, CustomContextParam, } from '../../src/conditions/context'; +import { RESERVED_CONTEXT_PARAMS } from '../../src/conditions/context/context'; import { paramOrContextParamSchema, ReturnValueTestProps, From dce202a5e9548e1b04b1d4ae11e992eab9916692 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 18 Jul 2024 19:21:19 -0400 Subject: [PATCH 09/21] Fix message to be use prefix constant. --- packages/taco/src/conditions/shared.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/taco/src/conditions/shared.ts b/packages/taco/src/conditions/shared.ts index 504cb2357..1e63b0560 100644 --- a/packages/taco/src/conditions/shared.ts +++ b/packages/taco/src/conditions/shared.ts @@ -15,7 +15,7 @@ export const plainStringSchema = z.string().refine( return !str.startsWith(CONTEXT_PARAM_PREFIX); }, { - message: 'String must not be a context parameter i.e. not start with ":"', + message: `String must not be a context parameter i.e. not start with "${CONTEXT_PARAM_PREFIX}"`, }, ); From 4f7c2ac189cef8988c4c497afaf384c3077e71d1 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 19 Jul 2024 11:07:26 -0400 Subject: [PATCH 10/21] Update nodejs example used for CI job to pass ConditionContext instead of directly passing auth provider. Expose USER_ADDRESS_PARAM* constants from taco for now. --- examples/taco/nodejs/src/index.ts | 6 +++++- packages/taco-auth/test/auth-provider.test.ts | 2 +- packages/taco/src/index.ts | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/taco/nodejs/src/index.ts b/examples/taco/nodejs/src/index.ts index 89b8bf25d..672562ea2 100644 --- a/examples/taco/nodejs/src/index.ts +++ b/examples/taco/nodejs/src/index.ts @@ -5,6 +5,7 @@ import { decrypt, domains, EIP4361AuthProvider, + USER_ADDRESS_PARAM_DEFAULT, encrypt, fromBytes, initialize, @@ -113,11 +114,14 @@ const decryptFromBytes = async (encryptedBytes: Uint8Array) => { consumerSigner, siweParams, ); + const conditionContext = + conditions.context.ConditionContext.fromMessageKit(messageKit); + conditionContext.addAuthProvider(USER_ADDRESS_PARAM_DEFAULT, authProvider); return decrypt( provider, domain, messageKit, - authProvider, + conditionContext, ); }; diff --git a/packages/taco-auth/test/auth-provider.test.ts b/packages/taco-auth/test/auth-provider.test.ts index d2b11a349..cf927b47b 100644 --- a/packages/taco-auth/test/auth-provider.test.ts +++ b/packages/taco-auth/test/auth-provider.test.ts @@ -7,8 +7,8 @@ import { import { SiweMessage } from 'siwe'; import { describe, expect, it } from 'vitest'; -import { EIP4361TypedDataSchema } from '../src/providers/eip4361/common'; import { EIP4361AuthProvider } from '../src/providers'; +import { EIP4361TypedDataSchema } from '../src/providers/eip4361/common'; describe('auth provider', () => { const provider = fakeProvider(bobSecretKeyBytes); diff --git a/packages/taco/src/index.ts b/packages/taco/src/index.ts index 315229937..20d7ea005 100644 --- a/packages/taco/src/index.ts +++ b/packages/taco/src/index.ts @@ -17,4 +17,6 @@ export { decrypt, encrypt, encryptWithPublicKey, isAuthorized } from './taco'; export { EIP4361AuthProvider, SingleSignOnEIP4361AuthProvider, + USER_ADDRESS_PARAM_DEFAULT, + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, } from '@nucypher/taco-auth'; From e613d3a6734db2c63a704624b39898e047b8c80b Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 19 Jul 2024 11:25:36 -0400 Subject: [PATCH 11/21] Appease linter. --- examples/taco/nodejs/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/taco/nodejs/src/index.ts b/examples/taco/nodejs/src/index.ts index 672562ea2..869e53aaf 100644 --- a/examples/taco/nodejs/src/index.ts +++ b/examples/taco/nodejs/src/index.ts @@ -1,11 +1,12 @@ import { format } from 'node:util'; import { + EIP4361AuthProvider, + ThresholdMessageKit, + USER_ADDRESS_PARAM_DEFAULT, conditions, decrypt, domains, - EIP4361AuthProvider, - USER_ADDRESS_PARAM_DEFAULT, encrypt, fromBytes, initialize, From e9e332fe0aa3b46c0096ab484886a3a8b363de0d Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 19 Jul 2024 11:26:09 -0400 Subject: [PATCH 12/21] Fix webpack-5 example to use ConditionContext instead of directly providing auth provider. --- examples/taco/webpack-5/src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/taco/webpack-5/src/index.ts b/examples/taco/webpack-5/src/index.ts index 3879dd04f..65ab50a2f 100644 --- a/examples/taco/webpack-5/src/index.ts +++ b/examples/taco/webpack-5/src/index.ts @@ -7,6 +7,7 @@ import { fromBytes, initialize, toBytes, + USER_ADDRESS_PARAM_DEFAULT, } from '@nucypher/taco'; import { ethers } from 'ethers'; import { hexlify } from 'ethers/lib/utils'; @@ -61,11 +62,14 @@ const runExample = async () => { console.log('Decrypting message...'); const authProvider = new EIP4361AuthProvider(provider, signer); + const conditionContext = + conditions.context.ConditionContext.fromMessageKit(messageKit); + conditionContext.addAuthProvider(USER_ADDRESS_PARAM_DEFAULT, authProvider); const decryptedBytes = await decrypt( provider, domain, messageKit, - authProvider, + conditionContext, ); const decryptedMessage = fromBytes(decryptedBytes); console.log('Decrypted message:', decryptedMessage); From 3c7cefb907eb7669c258a6431a80eeee6aa270cc Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 19 Jul 2024 11:36:11 -0400 Subject: [PATCH 13/21] Fix other taco examples to use ConditionContext instead of directly providing authProvider. --- examples/taco/nextjs/src/hooks/useTaco.ts | 9 ++++++++- examples/taco/react/src/hooks/useTaco.ts | 9 ++++++++- packages/taco/examples/encrypt-decrypt.ts | 6 +++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/examples/taco/nextjs/src/hooks/useTaco.ts b/examples/taco/nextjs/src/hooks/useTaco.ts index cc2996bf8..36a141603 100644 --- a/examples/taco/nextjs/src/hooks/useTaco.ts +++ b/examples/taco/nextjs/src/hooks/useTaco.ts @@ -6,6 +6,7 @@ import { encrypt, initialize, ThresholdMessageKit, + USER_ADDRESS_PARAM_DEFAULT, } from '@nucypher/taco'; import { ethers } from 'ethers'; import { useCallback, useEffect, useState } from 'react'; @@ -32,11 +33,17 @@ export default function useTaco({ } const messageKit = ThresholdMessageKit.fromBytes(encryptedBytes); const authProvider = new EIP4361AuthProvider(provider, signer); + const conditionContext = + conditions.context.ConditionContext.fromMessageKit(messageKit); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProvider, + ); return decrypt( provider, domain, messageKit, - authProvider, + conditionContext, ); }, [isInit, provider, domain], diff --git a/examples/taco/react/src/hooks/useTaco.ts b/examples/taco/react/src/hooks/useTaco.ts index cc2996bf8..36a141603 100644 --- a/examples/taco/react/src/hooks/useTaco.ts +++ b/examples/taco/react/src/hooks/useTaco.ts @@ -6,6 +6,7 @@ import { encrypt, initialize, ThresholdMessageKit, + USER_ADDRESS_PARAM_DEFAULT, } from '@nucypher/taco'; import { ethers } from 'ethers'; import { useCallback, useEffect, useState } from 'react'; @@ -32,11 +33,17 @@ export default function useTaco({ } const messageKit = ThresholdMessageKit.fromBytes(encryptedBytes); const authProvider = new EIP4361AuthProvider(provider, signer); + const conditionContext = + conditions.context.ConditionContext.fromMessageKit(messageKit); + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProvider, + ); return decrypt( provider, domain, messageKit, - authProvider, + conditionContext, ); }, [isInit, provider, domain], diff --git a/packages/taco/examples/encrypt-decrypt.ts b/packages/taco/examples/encrypt-decrypt.ts index a039efe7b..568b2b600 100644 --- a/packages/taco/examples/encrypt-decrypt.ts +++ b/packages/taco/examples/encrypt-decrypt.ts @@ -10,6 +10,7 @@ import { initialize, ThresholdMessageKit, toBytes, + USER_ADDRESS_PARAM_DEFAULT, } from '../src'; const ritualId = 1; @@ -49,11 +50,14 @@ const run = async () => { web3Provider, web3Provider.getSigner(), ); + const conditionContext = + conditions.context.ConditionContext.fromMessageKit(messageKit); + conditionContext.addAuthProvider(USER_ADDRESS_PARAM_DEFAULT, authProvider); const decryptedMessage = await decrypt( web3Provider, domains.TESTNET, messageKit, - authProvider, + conditionContext, ); return decryptedMessage; }; From cfc669dfa5742399ed37cfa8bf1503553dd70c0c Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 22 Jul 2024 15:27:08 -0400 Subject: [PATCH 14/21] Code cleanup based on RFC for #554. --- .../taco/src/conditions/context/context.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 3bc626005..1d8048082 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -37,6 +37,14 @@ const ERR_INVALID_AUTH_PROVIDER_TYPE = (param: string, expected: string) => const ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM = (param: string) => `AuthProvider not necessary for context parameter: ${param}`; +type AuthProviderType = + | typeof EIP4361AuthProvider + | typeof SingleSignOnEIP4361AuthProvider; +const EXPECTED_AUTH_PROVIDER_TYPES: Record = { + [USER_ADDRESS_PARAM_DEFAULT]: EIP4361AuthProvider, + [USER_ADDRESS_PARAM_EXTERNAL_EIP4361]: SingleSignOnEIP4361AuthProvider, +}; + export const RESERVED_CONTEXT_PARAMS = [ USER_ADDRESS_PARAM_EXTERNAL_EIP4361, USER_ADDRESS_PARAM_DEFAULT, @@ -183,26 +191,19 @@ export class ConditionContext { } public addAuthProvider(contextParam: string, authProvider: AuthProvider) { - if (!USER_ADDRESS_PARAMS.includes(contextParam)) { + if (!(contextParam in EXPECTED_AUTH_PROVIDER_TYPES)) { throw new Error( ERR_AUTH_PROVIDER_NOT_NEEDED_FOR_CONTEXT_PARAM(contextParam), ); } - if (contextParam === USER_ADDRESS_PARAM_DEFAULT) { - if (authProvider instanceof EIP4361AuthProvider) { - this.authProviders[contextParam] = authProvider; - return; - } - } else if (contextParam === USER_ADDRESS_PARAM_EXTERNAL_EIP4361) { - if (authProvider instanceof SingleSignOnEIP4361AuthProvider) { - this.authProviders[contextParam] = authProvider; - return; - } + if (!(authProvider instanceof EXPECTED_AUTH_PROVIDER_TYPES[contextParam])) { + throw new Error( + ERR_INVALID_AUTH_PROVIDER_TYPE(contextParam, typeof authProvider), + ); } - throw new Error( - ERR_INVALID_AUTH_PROVIDER_TYPE(contextParam, typeof authProvider), - ); + + this.authProviders[contextParam] = authProvider; } public async toJson(): Promise { From 8a751c66bd8877904897290b6097c038d96f6a03 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 22 Jul 2024 15:27:53 -0400 Subject: [PATCH 15/21] Add tests for processing addition of authProviders to ConditionContext. --- packages/taco/test/conditions/context.test.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index 796590cb3..a2d6f174b 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -354,6 +354,62 @@ describe('No authentication provider', () => { }); }); + it('rejects auth provider for not applicable context param', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: ':myParam', + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + expect(() => + conditionContext.addAuthProvider( + ':myParam', + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ), + ).toThrow('AuthProvider not necessary for context parameter: :myParam'); + }); + + it('rejects invalid auth provider for :userAddress', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: USER_ADDRESS_PARAM_DEFAULT, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + expect(() => + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_DEFAULT, + authProviders[USER_ADDRESS_PARAM_EXTERNAL_EIP4361], + ), + ).toThrow(`Invalid AuthProvider type for ${USER_ADDRESS_PARAM_DEFAULT}`); + }); + + it('rejects invalid auth provider for :userAddressExternalEIP4361', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionContext = new ConditionContext(condition); + expect(() => + conditionContext.addAuthProvider( + USER_ADDRESS_PARAM_EXTERNAL_EIP4361, + authProviders[USER_ADDRESS_PARAM_DEFAULT], + ), + ).toThrow( + `Invalid AuthProvider type for ${USER_ADDRESS_PARAM_EXTERNAL_EIP4361}`, + ); + }); + it('it supports just one provider at a time', async () => { const conditionObj = { ...testContractConditionObj, From 83585f4002a87c554e81257a363b11a3fa0d29ed Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 30 Jul 2024 08:53:16 -0400 Subject: [PATCH 16/21] Remove unnecessary import. --- examples/taco/nodejs/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/taco/nodejs/src/index.ts b/examples/taco/nodejs/src/index.ts index 869e53aaf..c38117dab 100644 --- a/examples/taco/nodejs/src/index.ts +++ b/examples/taco/nodejs/src/index.ts @@ -11,7 +11,6 @@ import { fromBytes, initialize, isAuthorized, - ThresholdMessageKit, toBytes, toHexString, } from '@nucypher/taco'; From 8c30bc4f376713548ce532de4c75207750ea2d9c Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 15 Aug 2024 16:59:27 -0400 Subject: [PATCH 17/21] Fix linting errors post-rebase. --- examples/taco/nextjs/src/hooks/useTaco.ts | 7 +------ examples/taco/nodejs/src/index.ts | 7 +------ examples/taco/react/src/hooks/useTaco.ts | 7 +------ packages/shared/src/porter.ts | 4 +--- packages/taco/src/taco.ts | 4 +++- packages/test-utils/src/utils.ts | 8 +++++--- 6 files changed, 12 insertions(+), 25 deletions(-) diff --git a/examples/taco/nextjs/src/hooks/useTaco.ts b/examples/taco/nextjs/src/hooks/useTaco.ts index 36a141603..abcd7efd8 100644 --- a/examples/taco/nextjs/src/hooks/useTaco.ts +++ b/examples/taco/nextjs/src/hooks/useTaco.ts @@ -39,12 +39,7 @@ export default function useTaco({ USER_ADDRESS_PARAM_DEFAULT, authProvider, ); - return decrypt( - provider, - domain, - messageKit, - conditionContext, - ); + return decrypt(provider, domain, messageKit, conditionContext); }, [isInit, provider, domain], ); diff --git a/examples/taco/nodejs/src/index.ts b/examples/taco/nodejs/src/index.ts index c38117dab..7b31aa5ca 100644 --- a/examples/taco/nodejs/src/index.ts +++ b/examples/taco/nodejs/src/index.ts @@ -117,12 +117,7 @@ const decryptFromBytes = async (encryptedBytes: Uint8Array) => { const conditionContext = conditions.context.ConditionContext.fromMessageKit(messageKit); conditionContext.addAuthProvider(USER_ADDRESS_PARAM_DEFAULT, authProvider); - return decrypt( - provider, - domain, - messageKit, - conditionContext, - ); + return decrypt(provider, domain, messageKit, conditionContext); }; const runExample = async () => { diff --git a/examples/taco/react/src/hooks/useTaco.ts b/examples/taco/react/src/hooks/useTaco.ts index 36a141603..abcd7efd8 100644 --- a/examples/taco/react/src/hooks/useTaco.ts +++ b/examples/taco/react/src/hooks/useTaco.ts @@ -39,12 +39,7 @@ export default function useTaco({ USER_ADDRESS_PARAM_DEFAULT, authProvider, ); - return decrypt( - provider, - domain, - messageKit, - conditionContext, - ); + return decrypt(provider, domain, messageKit, conditionContext); }, [isInit, provider, domain], ); diff --git a/packages/shared/src/porter.ts b/packages/shared/src/porter.ts index ac47d5171..0a13050d3 100644 --- a/packages/shared/src/porter.ts +++ b/packages/shared/src/porter.ts @@ -38,9 +38,7 @@ export const getPorterUri = async (domain: Domain): Promise => { return (await getPorterUris(domain))[0]; }; -export const getPorterUris = async ( - domain: Domain, -): Promise => { +export const getPorterUris = async (domain: Domain): Promise => { const fullList = []; const uri = defaultPorterUri[domain]; if (!uri) { diff --git a/packages/taco/src/taco.ts b/packages/taco/src/taco.ts index 33d26f302..e711ae6b9 100644 --- a/packages/taco/src/taco.ts +++ b/packages/taco/src/taco.ts @@ -140,7 +140,9 @@ export const decrypt = async ( context?: ConditionContext, porterUris?: string[], ): Promise => { - const porterUrisFull: string[] = porterUris ? porterUris : await getPorterUris(domain); + const porterUrisFull: string[] = porterUris + ? porterUris + : await getPorterUris(domain); const ritualId = await DkgCoordinatorAgent.getRitualIdFromPublicKey( provider, diff --git a/packages/test-utils/src/utils.ts b/packages/test-utils/src/utils.ts index b205c2c28..a19af1391 100644 --- a/packages/test-utils/src/utils.ts +++ b/packages/test-utils/src/utils.ts @@ -152,9 +152,11 @@ export const fakeUrsulas = (n = 4): Ursula[] => export const mockGetUrsulas = ( ursulas: Ursula[] = fakeUrsulas(), ): SpyInstance => { - return vi.spyOn(PorterClient.prototype, 'getUrsulas').mockImplementation(async () => { - return Promise.resolve(ursulas); - }); + return vi + .spyOn(PorterClient.prototype, 'getUrsulas') + .mockImplementation(async () => { + return Promise.resolve(ursulas); + }); }; const fakeCFragResponse = ( From 81d9006dffcfb2c8d4fd724537828c92bd417d6d Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 16 Aug 2024 08:32:03 -0400 Subject: [PATCH 18/21] Construct a PorterClient earlier in the call stack for decrypt(), and then pass the client to calls later in the stack instead of passing around the porterUri(s). --- packages/taco/src/taco.ts | 4 +++- packages/taco/src/tdec.ts | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/taco/src/taco.ts b/packages/taco/src/taco.ts index e711ae6b9..fc166bf00 100644 --- a/packages/taco/src/taco.ts +++ b/packages/taco/src/taco.ts @@ -11,6 +11,7 @@ import { fromHexString, getPorterUris, GlobalAllowListAgent, + PorterClient, toBytes, } from '@nucypher/shared'; import { ethers } from 'ethers'; @@ -143,6 +144,7 @@ export const decrypt = async ( const porterUrisFull: string[] = porterUris ? porterUris : await getPorterUris(domain); + const porter = new PorterClient(porterUrisFull); const ritualId = await DkgCoordinatorAgent.getRitualIdFromPublicKey( provider, @@ -152,7 +154,7 @@ export const decrypt = async ( return retrieveAndDecrypt( provider, domain, - porterUrisFull, + porter, messageKit, ritualId, context, diff --git a/packages/taco/src/tdec.ts b/packages/taco/src/tdec.ts index 7b33f9ccc..97132437d 100644 --- a/packages/taco/src/tdec.ts +++ b/packages/taco/src/tdec.ts @@ -61,7 +61,7 @@ export const encryptMessage = async ( export const retrieveAndDecrypt = async ( provider: ethers.providers.Provider, domain: Domain, - porterUris: string[], + porter: PorterClient, thresholdMessageKit: ThresholdMessageKit, ritualId: number, context?: ConditionContext, @@ -69,7 +69,7 @@ export const retrieveAndDecrypt = async ( const decryptionShares = await retrieve( provider, domain, - porterUris, + porter, thresholdMessageKit, ritualId, context, @@ -82,7 +82,7 @@ export const retrieveAndDecrypt = async ( const retrieve = async ( provider: ethers.providers.Provider, domain: Domain, - porterUris: string[], + porter: PorterClient, thresholdMessageKit: ThresholdMessageKit, ritualId: number, context?: ConditionContext, @@ -106,7 +106,6 @@ const retrieve = async ( thresholdMessageKit, ); - const porter = new PorterClient(porterUris); const { encryptedResponses, errors } = await porter.tacoDecrypt( encryptedRequests, ritual.threshold, From e872fff53b8440aeda6a9da6d472eca58aff887d Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 16 Aug 2024 09:00:12 -0400 Subject: [PATCH 19/21] Update variable naming for ConditionContext to clarify that they are related to context parameters. --- .../taco/src/conditions/context/context.ts | 35 ++++++++++--------- packages/taco/test/conditions/context.test.ts | 14 +++++--- packages/taco/test/taco.test.ts | 2 +- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/taco/src/conditions/context/context.ts b/packages/taco/src/conditions/context/context.ts index 1d8048082..8b4992b8a 100644 --- a/packages/taco/src/conditions/context/context.ts +++ b/packages/taco/src/conditions/context/context.ts @@ -51,14 +51,14 @@ export const RESERVED_CONTEXT_PARAMS = [ ]; export class ConditionContext { - public requestedParameters: Set; - private customParameters: Record = {}; + public requestedContextParameters: Set; + private customContextParameters: Record = {}; private authProviders: Record = {}; constructor(condition: Condition) { const condProps = condition.toObj(); ConditionContext.validateCoreConditions(condProps); - this.requestedParameters = + this.requestedContextParameters = ConditionContext.findContextParameters(condProps); } @@ -73,27 +73,28 @@ export class ConditionContext { ) { // Ok, so at this point we should have all the parameters we need // If we don't, we have a problem and we should throw - const missingParameters = Array.from(this.requestedParameters).filter( - (key) => parameters[key] === undefined, - ); + const missingParameters = Array.from( + this.requestedContextParameters, + ).filter((key) => parameters[key] === undefined); if (missingParameters.length > 0) { throw new Error(ERR_MISSING_CONTEXT_PARAMS(missingParameters)); } } private async fillContextParameters( - requestedParameters: Set, + requestedContextParameters: Set, ): Promise> { - const parameters = - await this.fillAuthContextParameters(requestedParameters); - for (const key in this.customParameters) { - parameters[key] = this.customParameters[key]; + const parameters = await this.fillAuthContextParameters( + requestedContextParameters, + ); + for (const key in this.customContextParameters) { + parameters[key] = this.customContextParameters[key]; } return parameters; } private validateAuthProviders(): void { - for (const param of this.requestedParameters) { + for (const param of this.requestedContextParameters) { // If it's not a user address parameter, we can skip if (!USER_ADDRESS_PARAMS.includes(param)) { continue; @@ -131,7 +132,7 @@ export class ConditionContext { throw new Error(ERR_RESERVED_PARAM(customParam)); } - if (!this.requestedParameters.has(customParam)) { + if (!this.requestedContextParameters.has(customParam)) { throw new Error(ERR_UNKNOWN_CUSTOM_CONTEXT_PARAM(customParam)); } } @@ -182,11 +183,11 @@ export class ConditionContext { } public addCustomContextParameterValues( - customParameters: Record, + customContextParameters: Record, ) { - Object.keys(customParameters).forEach((key) => { + Object.keys(customContextParameters).forEach((key) => { this.validateCustomContextParameter(key); - this.customParameters[key] = customParameters[key]; + this.customContextParameters[key] = customContextParameters[key]; }); } @@ -221,7 +222,7 @@ export class ConditionContext { > => { this.validateAuthProviders(); const parameters = await this.fillContextParameters( - this.requestedParameters, + this.requestedContextParameters, ); this.validateNoMissingContextParameters(parameters); return parameters; diff --git a/packages/taco/test/conditions/context.test.ts b/packages/taco/test/conditions/context.test.ts index a2d6f174b..d18758554 100644 --- a/packages/taco/test/conditions/context.test.ts +++ b/packages/taco/test/conditions/context.test.ts @@ -82,7 +82,9 @@ describe('context', () => { describe('custom parameters', () => { it('detects when a custom parameter is requested', () => { const conditionContext = new ConditionContext(contractCondition); - expect(conditionContext.requestedParameters).toContain(customParamKey); + expect(conditionContext.requestedContextParameters).toContain( + customParamKey, + ); }); it('serializes bytes as hex strings', async () => { @@ -232,10 +234,12 @@ describe('context', () => { }; it('handles both custom and auth context parameters', () => { - const requestedParams = new ConditionContext(contractCondition) - .requestedParameters; - expect(requestedParams).not.toContain(USER_ADDRESS_PARAM_DEFAULT); - expect(requestedParams).toContain(customParamKey); + const requestedContextParams = new ConditionContext(contractCondition) + .requestedContextParameters; + expect(requestedContextParams).not.toContain( + USER_ADDRESS_PARAM_DEFAULT, + ); + expect(requestedContextParams).toContain(customParamKey); }); it('rejects on a missing custom parameter ', async () => { diff --git a/packages/taco/test/taco.test.ts b/packages/taco/test/taco.test.ts index cbb0a91da..589d7a9fa 100644 --- a/packages/taco/test/taco.test.ts +++ b/packages/taco/test/taco.test.ts @@ -132,7 +132,7 @@ describe('taco', () => { expect(getFinalizedRitualSpy).toHaveBeenCalled(); const conditionContext = ConditionContext.fromMessageKit(messageKit); - const requestedParameters = conditionContext.requestedParameters; + const requestedParameters = conditionContext.requestedContextParameters; expect(requestedParameters).toEqual( new Set([customParamKey, USER_ADDRESS_PARAM_DEFAULT]), ); From 99732a9f94b5fdc26a06d44c224a54fe2fe46eb4 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 16 Aug 2024 16:09:54 -0400 Subject: [PATCH 20/21] Fix failing porter test now that the dictionary of URIs from github is empty. --- packages/shared/test/porter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/test/porter.test.ts b/packages/shared/test/porter.test.ts index 19d724667..dbe73aab6 100644 --- a/packages/shared/test/porter.test.ts +++ b/packages/shared/test/porter.test.ts @@ -57,7 +57,7 @@ describe('getPorterUris', () => { it('Get URIs from source', async () => { for (const domain of Object.values(domains)) { const uris = await getPorterUrisFromSource(domain); - expect(uris.length).toBeGreaterThan(0); + expect(uris.length).toBeGreaterThanOrEqual(0); const fullList = await getPorterUris(domain); expect(fullList).toEqual(expect.arrayContaining(uris)); } From 1e528aa196c4a774a192a4dcde1d1f09e31d06cf Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 19 Aug 2024 09:48:12 -0400 Subject: [PATCH 21/21] Add illustrative example of using requestedContextParameters property on ConditionContext. --- examples/taco/nodejs/src/index.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/taco/nodejs/src/index.ts b/examples/taco/nodejs/src/index.ts index 7b31aa5ca..37e8974a7 100644 --- a/examples/taco/nodejs/src/index.ts +++ b/examples/taco/nodejs/src/index.ts @@ -109,14 +109,21 @@ const decryptFromBytes = async (encryptedBytes: Uint8Array) => { domain: 'localhost', uri: 'http://localhost:3000', }; - const authProvider = new EIP4361AuthProvider( - provider, - consumerSigner, - siweParams, - ); const conditionContext = conditions.context.ConditionContext.fromMessageKit(messageKit); - conditionContext.addAuthProvider(USER_ADDRESS_PARAM_DEFAULT, authProvider); + + // illustrative optional example of checking what context parameters are required + // unnecessary if you already know what the condition contains + if ( + conditionContext.requestedContextParameters.has(USER_ADDRESS_PARAM_DEFAULT) + ) { + const authProvider = new EIP4361AuthProvider( + provider, + consumerSigner, + siweParams, + ); + conditionContext.addAuthProvider(USER_ADDRESS_PARAM_DEFAULT, authProvider); + } return decrypt(provider, domain, messageKit, conditionContext); };