Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose auth providers from taco #534

Merged
merged 4 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/taco/nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@nucypher/taco": "workspace:*",
"@nucypher/taco-auth": "workspace:*",
"dotenv": "^16.3.1",
"ethers": "^5.7.2"
}
Expand Down
11 changes: 8 additions & 3 deletions examples/taco/nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { format } from 'node:util';

import {
conditions,
decrypt,
decryptWithAuthProviders,
domains,
encrypt,
fromBytes,
Expand All @@ -12,6 +12,7 @@ import {
toBytes,
toHexString,
} from '@nucypher/taco';
import { makeAuthProviders } from '@nucypher/taco-auth';
import * as dotenv from 'dotenv';
import { ethers } from 'ethers';

Expand Down Expand Up @@ -84,12 +85,16 @@ const decryptFromBytes = async (encryptedBytes: Uint8Array) => {

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

Expand Down
3 changes: 3 additions & 0 deletions examples/taco/nodejs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
{
"path": "../../../packages/taco/tsconfig.cjs.json",
},
{
"path": "../../../packages/taco-auth/tsconfig.cjs.json",
}
],
}
45 changes: 21 additions & 24 deletions packages/taco-auth/src/providers/eip4361.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,33 @@ 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
const maybeOrigin = window?.location?.origin;
return {
domain: maybeOrigin.split('//')[1].split('.')[0],
uri: maybeOrigin,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Judging by this code here - https://1.x.wagmi.sh/examples/sign-in-with-ethereum#step-3-sign--verify-message, assuming window.location is available, can we just use the host property for the domain value instead of the parsing?

Also, out of curiosity is document an option in addition to window? Noticed this here - https://github.com/spruceid/siwe-notepad/blob/main/src/providers.ts#L132. Not sure what environment document is available ... 🤔

Copy link
Contributor Author

@piotr-roslaniec piotr-roslaniec Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, document and window are globally available objects provided by the browser environment.

I updated this snippet.

}
// If not, we have no choice but to throw an error
throw new Error(ERR_MISSING_SIWE_PARAMETERS);
piotr-roslaniec marked this conversation as resolved.
Show resolved Hide resolved
}

public async getOrCreateAuthSignature(): Promise<AuthSignature> {
Expand All @@ -46,7 +65,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;
Copy link
Member

@derekpierre derekpierre Jul 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do signer.getChainId( ) here? Then we can get rid of provider from the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not every signer has a connected provider. In ethers, it's possible to create a signer using a raw private key without ever connecting to an RPC.

I think we may want to explore requiring that every signer has a provider already connected and to do that in the whole codebase. But I think this is for another PR since we would be changing quite a lot of APIs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok.

Expand All @@ -64,26 +83,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);
}
}
2 changes: 1 addition & 1 deletion packages/taco/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ export {

export * as conditions from './conditions';
// Expose registerEncrypters from taco API (#324)
export { decrypt, encrypt, encryptWithPublicKey, isAuthorized } from './taco';
export { decrypt, decryptWithAuthProviders, encrypt, encryptWithPublicKey, isAuthorized } from './taco';
25 changes: 21 additions & 4 deletions packages/taco/src/taco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
GlobalAllowListAgent,
toBytes,
} from '@nucypher/shared';
import { makeAuthProviders } from '@nucypher/taco-auth';
import { AuthProviders, makeAuthProviders } from '@nucypher/taco-auth';
import { ethers } from 'ethers';
import { keccak256 } from 'ethers/lib/utils';

Expand Down Expand Up @@ -143,6 +143,25 @@ export const decrypt = async (
porterUri?: string,
signer?: ethers.Signer,
customParameters?: Record<string, CustomContextParam>,
): Promise<Uint8Array> => {
const authProviders = makeAuthProviders(provider, signer);
return decryptWithAuthProviders(
provider,
domain,
messageKit,
authProviders,
porterUri,
customParameters,
);
};

export const decryptWithAuthProviders = async (
piotr-roslaniec marked this conversation as resolved.
Show resolved Hide resolved
provider: ethers.providers.Provider,
domain: Domain,
messageKit: ThresholdMessageKit,
authProviders?: AuthProviders,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interested to see what this parameter looks like when EIP712 eventually gets removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't foresee new providers near-term, it could be just authProvider: EIP4361Provider

porterUri?: string,
customParameters?: Record<string, CustomContextParam>,
): Promise<Uint8Array> => {
if (!porterUri) {
porterUri = getPorterUri(domain);
Expand All @@ -154,8 +173,6 @@ 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);
return retrieveAndDecrypt(
provider,
domain,
Expand Down Expand Up @@ -188,7 +205,7 @@ export const isAuthorized = async (
domain: Domain,
messageKit: ThresholdMessageKit,
ritualId: number,
) =>
): Promise<boolean> =>
DkgCoordinatorAgent.isEncryptionAuthorized(
provider,
domain,
Expand Down
31 changes: 22 additions & 9 deletions packages/taco/test/taco.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
SessionStaticSecret,
} from '@nucypher/nucypher-core';
import { USER_ADDRESS_PARAM_DEFAULT } from '@nucypher/taco-auth';
import * as tacoAuth from '@nucypher/taco-auth';
import {
aliceSecretKeyBytes,
fakeDkgFlow,
Expand All @@ -13,9 +14,9 @@ import {
fakeTDecFlow,
mockGetRitualIdFromPublicKey,
mockTacoDecrypt,
TEST_CHAIN_ID,
TEST_CHAIN_ID, TEST_SIWE_PARAMS,
} from '@nucypher/test-utils';
import { beforeAll, describe, expect, it } from 'vitest';
import { beforeAll, describe, expect, it, vi } from 'vitest';

import * as taco from '../src';
import { conditions, domains, toBytes } from '../src';
Expand All @@ -36,6 +37,7 @@ const ownsNFT = new conditions.predefined.erc721.ERC721Ownership({
chain: TEST_CHAIN_ID,
});


describe('taco', () => {
beforeAll(async () => {
await initialize();
Expand Down Expand Up @@ -81,30 +83,41 @@ describe('taco', () => {
);
const getRitualSpy = mockGetActiveRitual(mockedDkgRitual);

const decryptedMessage = await taco.decrypt(
const authProviders = tacoAuth.makeAuthProviders(provider, signer,TEST_SIWE_PARAMS);
const decryptedMessage1 = await taco.decryptWithAuthProviders(
provider,
domains.DEVNET,
messageKit,
authProviders,
fakePorterUri,
signer,
);
expect(decryptedMessage1).toEqual(toBytes(message));
expect(getParticipantsSpy).toHaveBeenCalled();
expect(sessionKeySpy).toHaveBeenCalled();
expect(getRitualIdFromPublicKey).toHaveBeenCalled();
expect(getRitualSpy).toHaveBeenCalled();
expect(decryptSpy).toHaveBeenCalled();
expect(decryptedMessage).toEqual(toBytes(message));

const makeAuthProvidersSpy = vi.spyOn(tacoAuth, 'makeAuthProviders').mockImplementation(() => authProviders);
const decryptedMessage2 = await taco.decrypt(
provider,
domains.DEVNET,
messageKit,
fakePorterUri,
signer,
);
expect(makeAuthProvidersSpy).toHaveBeenCalled();
expect(decryptedMessage2).toEqual(toBytes(message));
});

it('exposes requested parameters', async ()=> {
it('exposes requested parameters', async () => {
const mockedDkg = fakeDkgFlow(FerveoVariant.precomputed, 0, 4, 4);
const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const provider = fakeProvider(aliceSecretKeyBytes);
const signer = fakeSigner(aliceSecretKeyBytes);
const getFinalizedRitualSpy = mockGetActiveRitual(mockedDkgRitual);


const customParamKey = ":nftId";
const customParamKey = ':nftId';
const ownsNFTWithCustomParams = new conditions.predefined.erc721.ERC721Ownership({
contractAddress: '0x1e988ba4692e52Bc50b375bcC8585b95c48AaD77',
parameters: [customParamKey],
Expand All @@ -123,5 +136,5 @@ describe('taco', () => {

const requestedParameters = taco.conditions.context.ConditionContext.requestedContextParameters(messageKit);
expect(requestedParameters).toEqual(new Set([customParamKey, USER_ADDRESS_PARAM_DEFAULT]));
})
});
});
1 change: 1 addition & 0 deletions packages/taco/test/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { ReturnValueTestProps } from '../src/conditions/shared';
import { DkgClient, DkgRitual } from '../src/dkg';
import { encryptMessage } from '../src/tdec';


export const fakeDkgTDecFlowE2E: (
ritualId?: number,
variant?: FerveoVariant,
Expand Down
4 changes: 3 additions & 1 deletion packages/test-utils/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import axios from 'axios';
import { ethers, providers, Wallet } from 'ethers';
import { expect, SpyInstance, vi } from 'vitest';

import { TEST_SIWE_PARAMS } from './variables';

export const bytesEqual = (first: Uint8Array, second: Uint8Array): boolean =>
first.length === second.length &&
first.every((value, index) => value === second[index]);
Expand Down Expand Up @@ -84,7 +86,7 @@ export const fakeSigner = (
} as unknown as ethers.providers.JsonRpcSigner;
};

export const fakeAuthProviders = () => makeAuthProviders(fakeProvider(), fakeSigner());
export const fakeAuthProviders = () => makeAuthProviders(fakeProvider(), fakeSigner(), TEST_SIWE_PARAMS);

export const fakeProvider = (
secretKeyBytes = SecretKey.random().toBEBytes(),
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading