Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

Commit

Permalink
Expose auth providers from taco (nucypher#534)
Browse files Browse the repository at this point in the history
  • Loading branch information
derekpierre authored Jul 10, 2024
2 parents f9e2030 + 0fba5c5 commit 28b3592
Show file tree
Hide file tree
Showing 16 changed files with 98 additions and 72 deletions.
6 changes: 3 additions & 3 deletions demos/taco-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import { ethers } from 'ethers';
import React, { useEffect, useState } from 'react';

import { ConditionBuilder } from './ConditionBuilder';
import { DEFAULT_DOMAIN, DEFAULT_RITUAL_ID } from './config';
import { Decrypt } from './Decrypt';
import { Encrypt } from './Encrypt';
import { downloadData, getWebIrys, uploadData } from './irys';
import { Spinner } from './Spinner';
import { DEFAULT_DOMAIN, DEFAULT_RITUAL_ID } from './config';
import { downloadData, getWebIrys, uploadData } from './irys';

const chainIdForDomain = {
[domains.DEVNET]: 80002,
Expand Down Expand Up @@ -121,7 +121,7 @@ export default function App() {
<h2>Notice</h2>
<p>
In production (mainnet domain), your wallet address (encryptor) will also have
to be allow-listed for this specific ritual. However, we have
to be allow-listed for this specific ritual. However, we have
<a href={'https://docs.threshold.network/app-development/threshold-access-control-tac/integration-guide/get-started-with-tac#testnet-configuration'}>publicly available testnet rituals</a>
for use when developing your apps.
</p>
Expand Down
10 changes: 7 additions & 3 deletions examples/taco/nextjs/src/hooks/useTaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
conditions,
decrypt,
Domain,
EIP4361AuthProvider,
encrypt,
getPorterUri,
initialize,
Expand All @@ -26,15 +27,18 @@ export default function useTaco({
}, []);

const decryptDataFromBytes = useCallback(
async (encryptedBytes: Uint8Array, signer?: ethers.Signer) => {
if (!isInit || !provider) return;
async (encryptedBytes: Uint8Array, signer: ethers.Signer) => {
if (!isInit || !provider) {
return;
}
const messageKit = ThresholdMessageKit.fromBytes(encryptedBytes);
const authProvider = new EIP4361AuthProvider(provider, signer);
return decrypt(
provider,
domain,
messageKit,
authProvider,
getPorterUri(domain),
signer,
);
},
[isInit, provider, domain],
Expand Down
3 changes: 0 additions & 3 deletions examples/taco/nextjs/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,5 @@
{
"path": "../../../packages/taco/tsconfig.es.json"
},
{
"path": "../../../packages/taco-auth/tsconfig.es.json"
}
]
}
8 changes: 7 additions & 1 deletion examples/taco/nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
conditions,
decrypt,
domains,
EIP4361AuthProvider,
encrypt,
fromBytes,
getPorterUri,
Expand Down Expand Up @@ -84,12 +85,17 @@ const decryptFromBytes = async (encryptedBytes: Uint8Array) => {

const messageKit = ThresholdMessageKit.fromBytes(encryptedBytes);
console.log('Decrypting message ...');
const siweParams = {
domain: 'localhost',
uri: 'http://localhost:3000',
};
const authProvider = new EIP4361AuthProvider(provider, consumerSigner, siweParams);
return decrypt(
provider,
domain,
messageKit,
authProvider,
getPorterUri(domain),
consumerSigner,
);
};

Expand Down
10 changes: 7 additions & 3 deletions examples/taco/react/src/hooks/useTaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
conditions,
decrypt,
Domain,
EIP4361AuthProvider,
encrypt,
getPorterUri,
initialize,
Expand All @@ -26,15 +27,18 @@ export default function useTaco({
}, []);

const decryptDataFromBytes = useCallback(
async (encryptedBytes: Uint8Array, signer?: ethers.Signer) => {
if (!isInit || !provider) return;
async (encryptedBytes: Uint8Array, signer: ethers.Signer) => {
if (!isInit || !provider) {
return;
}
const messageKit = ThresholdMessageKit.fromBytes(encryptedBytes);
const authProvider = new EIP4361AuthProvider(provider, signer);
return decrypt(
provider,
domain,
messageKit,
authProvider,
getPorterUri(domain),
signer,
);
},
[isInit, provider, domain],
Expand Down
2 changes: 1 addition & 1 deletion examples/taco/react/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
"references": [
{
"path": "../../../packages/taco/tsconfig.es.json"
}
},
]
}
4 changes: 3 additions & 1 deletion examples/taco/webpack-5/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
conditions,
decrypt,
domains,
EIP4361AuthProvider,
encrypt,
fromBytes,
getPorterUri,
Expand Down Expand Up @@ -60,12 +61,13 @@ const runExample = async () => {
);

console.log('Decrypting message...');
const authProvider = new EIP4361AuthProvider(provider, signer);
const decryptedBytes = await decrypt(
provider,
domain,
messageKit,
authProvider,
getPorterUri(domain),
signer,
);
const decryptedMessage = fromBytes(decryptedBytes);
console.log('Decrypted message:', decryptedMessage);
Expand Down
2 changes: 1 addition & 1 deletion packages/taco-auth/src/auth-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const USER_ADDRESS_PARAM_EIP712 = `:userAddress${EIP712_AUTH_METHOD}`;
export const USER_ADDRESS_PARAM_EIP4361 = `:userAddress${EIP4361_AUTH_METHOD}`;

export const AUTH_METHOD_FOR_PARAM: Record<string, string> = {
[USER_ADDRESS_PARAM_DEFAULT]: EIP712_AUTH_METHOD,
[USER_ADDRESS_PARAM_DEFAULT]: EIP4361_AUTH_METHOD,
[USER_ADDRESS_PARAM_EIP712]: EIP712_AUTH_METHOD,
[USER_ADDRESS_PARAM_EIP4361]: EIP4361_AUTH_METHOD,
};
44 changes: 20 additions & 24 deletions packages/taco-auth/src/providers/eip4361.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,32 @@ const ERR_MISSING_SIWE_PARAMETERS = 'Missing default SIWE parameters';

export class EIP4361AuthProvider {
private readonly storage: LocalStorage;
private readonly providerParams: EIP4361AuthProviderParams;

constructor(
// TODO: We only need the provider to fetch the chainId, consider removing it
private readonly provider: ethers.providers.Provider,
private readonly signer: ethers.Signer,
private readonly providerParams?: EIP4361AuthProviderParams,
providerParams?: EIP4361AuthProviderParams,
) {
this.storage = new LocalStorage();
if (providerParams) {
this.providerParams = providerParams;
} else {
this.providerParams = this.getDefaultParameters();
}
}

private getDefaultParameters() {
if (typeof window !== 'undefined') {
// If we are in a browser environment, we can get the domain and uri from the window object
return {
domain: window.location?.host,
uri: window.location?.origin,
};
}
// If not, we have no choice but to throw an error
throw new Error(ERR_MISSING_SIWE_PARAMETERS);
}

public async getOrCreateAuthSignature(): Promise<AuthSignature> {
Expand All @@ -55,7 +73,7 @@ export class EIP4361AuthProvider {

private async createSIWEAuthMessage(): Promise<AuthSignature> {
const address = await this.signer.getAddress();
const { domain, uri } = this.getParametersOrDefault();
const { domain, uri } = this.providerParams;
const version = '1';
const nonce = generateNonce();
const chainId = (await this.provider.getNetwork()).chainId;
Expand All @@ -73,26 +91,4 @@ export class EIP4361AuthProvider {
const signature = await this.signer.signMessage(message);
return { signature, address, scheme, typedData: message };
}

// TODO: Create a facility to set these parameters or expose them to the user
private getParametersOrDefault(): {
domain: string;
uri: string;
} {
// If we are in a browser environment, we can get the domain and uri from the window object
if (typeof window !== 'undefined') {
const maybeOrigin = window?.location?.origin;
return {
domain: maybeOrigin.split('//')[1].split('.')[0],
uri: maybeOrigin,
};
}
if (this.providerParams) {
return {
domain: this.providerParams.domain,
uri: this.providerParams.uri,
};
}
throw new Error(ERR_MISSING_SIWE_PARAMETERS);
}
}
7 changes: 4 additions & 3 deletions packages/taco/examples/encrypt-decrypt.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ChainId } from '@nucypher/shared';
import { ethers } from 'ethers';

import { ChainId } from '@nucypher/shared';
import {
conditions,
decrypt,
domains,
domains, EIP4361AuthProvider,
encrypt,
getPorterUri,
initialize,
Expand Down Expand Up @@ -45,12 +45,13 @@ const run = async () => {

// @ts-ignore
const web3Provider = new ethers.providers.Web3Provider(window.ethereum);
const authProvider = new EIP4361AuthProvider(web3Provider, web3Provider.getSigner());
const decryptedMessage = await decrypt(
web3Provider,
domains.TESTNET,
messageKit,
authProvider,
getPorterUri(domains.TESTNET),
web3Provider.getSigner(),
);
return decryptedMessage;
};
Expand Down
5 changes: 4 additions & 1 deletion packages/taco/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ export {
} from '@nucypher/shared';

export * as conditions from './conditions';
// Expose registerEncrypters from taco API (#324)
// TODO(#324): Expose registerEncrypters from taco API
export { decrypt, encrypt, encryptWithPublicKey, isAuthorized } from './taco';

// TODO: Remove this re-export once `@nucypher/taco-auth` is mature and published
export { EIP4361AuthProvider } from '@nucypher/taco-auth';
22 changes: 14 additions & 8 deletions packages/taco/src/taco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ import {
GlobalAllowListAgent,
toBytes,
} from '@nucypher/shared';
import { makeAuthProviders } from '@nucypher/taco-auth';
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 { CustomContextParam } from './conditions/context';
import { DkgClient } from './dkg';
import { retrieveAndDecrypt } from './tdec';

Expand Down Expand Up @@ -125,9 +129,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} [porterUri] - The URI for the Porter service. If not provided, a value will be obtained
* from the Domain
* @param {ethers.Signer} [signer] - An optional signer for the decryption
* @param {Record<string, CustomContextParam>} [customParameters] - Optional custom parameters that may be required
* depending on the condition used
*
Expand All @@ -140,8 +144,8 @@ export const decrypt = async (
provider: ethers.providers.Provider,
domain: Domain,
messageKit: ThresholdMessageKit,
authProvider?: EIP4361AuthProvider,
porterUri?: string,
signer?: ethers.Signer,
customParameters?: Record<string, CustomContextParam>,
): Promise<Uint8Array> => {
if (!porterUri) {
Expand All @@ -154,8 +158,11 @@ export const decrypt = async (
messageKit.acp.publicKey,
);
const ritual = await DkgClient.getActiveRitual(provider, domain, ritualId);
// TODO: Temporary helper method to keep the external taco.ts decrypt function simple
const authProviders = makeAuthProviders(provider, signer);
const authProviders: AuthProviders = authProvider
? {
[EIP4361_AUTH_METHOD]: authProvider,
}
: {};
return retrieveAndDecrypt(
provider,
domain,
Expand All @@ -169,7 +176,6 @@ export const decrypt = async (
);
};


/**
* Checks if the encryption from the provided messageKit is authorized for the specified ritual.
*
Expand All @@ -188,7 +194,7 @@ export const isAuthorized = async (
domain: Domain,
messageKit: ThresholdMessageKit,
ritualId: number,
) =>
): Promise<boolean> =>
DkgCoordinatorAgent.isEncryptionAuthorized(
provider,
domain,
Expand Down
26 changes: 14 additions & 12 deletions packages/taco/test/conditions/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,24 +405,26 @@ describe('No authentication provider', () => {
expect(eip712Spy).toHaveBeenCalledOnce();
}

it('supports default auth method (eip712)', async () => {
await testEIP712AuthMethod(USER_ADDRESS_PARAM_DEFAULT);
async function testEIP4361AuthMethod(authMethod: string) {
const eip4361Spy = vi.spyOn(
EIP4361AuthProvider.prototype,
'getOrCreateAuthSignature',
);
const authSignature = await makeAuthSignature(authMethod);
await testEIP4361AuthSignature(authSignature);
expect(eip4361Spy).toHaveBeenCalledOnce();
}

it('supports default auth method (eip4361)', async () => {
await testEIP4361AuthMethod(USER_ADDRESS_PARAM_DEFAULT);
});

it('supports eip712', async () => {
await testEIP712AuthMethod(USER_ADDRESS_PARAM_EIP712);
});

it('supports eip4361', async () => {
const eip4361Spy = vi.spyOn(
EIP4361AuthProvider.prototype,
'getOrCreateAuthSignature',
);

const authSignature = await makeAuthSignature(USER_ADDRESS_PARAM_EIP4361);
await testEIP4361AuthSignature(authSignature);

expect(eip4361Spy).toHaveBeenCalledOnce();
await testEIP4361AuthMethod(USER_ADDRESS_PARAM_EIP4361);
});

it('supports reusing external eip4361', async () => {
Expand Down Expand Up @@ -464,7 +466,7 @@ describe('No authentication provider', () => {
authProviders,
);
const contextVars = await builtContext.toContextParameters();
expect(eip4361Spy).not.toHaveBeenCalledOnce();
expect(eip4361Spy).not.toHaveBeenCalled();

// Now, we expect that the auth signature will be available in the context variables
const authSignature = contextVars[
Expand Down
Loading

0 comments on commit 28b3592

Please sign in to comment.