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

PoC for Encryptor Signature Self-Delegation Authentication #601

Draft
wants to merge 5 commits into
base: epic-v0.6.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 8 additions & 2 deletions examples/taco/nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import {
} from '@nucypher/taco';
import {
EIP4361AuthProvider,
SelfDelegateProvider,
USER_ADDRESS_PARAM_DEFAULT,
} from '@nucypher/taco-auth';
import * as dotenv from 'dotenv';
import { ethers } from 'ethers';
import { Wallet, ethers } from 'ethers';

dotenv.config();

Expand Down Expand Up @@ -73,13 +74,18 @@ const encryptToBytes = async (messageString: string) => {
'Condition requires authentication',
);

const ephemeralPrivateKey = Wallet.createRandom().privateKey;
const selfDelegateProvider = new SelfDelegateProvider(encryptorSigner);

const appSideSigner = await selfDelegateProvider.createSelfDelegatedAppSideSigner(ephemeralPrivateKey);

const messageKit = await encrypt(
provider,
domain,
message,
hasPositiveBalance,
ritualId,
encryptorSigner,
appSideSigner,
);

return messageKit.toBytes();
Expand Down
9 changes: 7 additions & 2 deletions packages/taco-auth/src/auth-sig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import {
EIP4361_AUTH_METHOD,
EIP4361TypedDataSchema,
} from './providers/eip4361/common';
import {
ENCRYPTOR_SELF_DELEGATE_AUTH_METHOD,
SelfDelegateTypedDataSchema,
} from './providers/encryptor/self-delegate';

// TODO: Create two different schemas, rather than one with different field schemas
export const authSignatureSchema = z.object({
signature: z.string(),
address: EthAddressSchema,
scheme: z.enum([EIP4361_AUTH_METHOD]),
typedData: EIP4361TypedDataSchema,
scheme: z.enum([EIP4361_AUTH_METHOD, ENCRYPTOR_SELF_DELEGATE_AUTH_METHOD]),
typedData: z.union([EIP4361TypedDataSchema, SelfDelegateTypedDataSchema]),
});

export type AuthSignature = z.infer<typeof authSignatureSchema>;
80 changes: 80 additions & 0 deletions packages/taco-auth/src/providers/encryptor/self-delegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ethers, Wallet } from 'ethers';
import { z } from 'zod';

// import { Bytes } from "@ethersproject/bytes";

import { AuthSignature } from '../../auth-sig';
import { LocalStorage } from '../../storage';

export const ENCRYPTOR_SELF_DELEGATE_AUTH_METHOD = 'EncryptorSelfDelegate'

export const SelfDelegateTypedDataSchema = z.string();


// TODO: Create generic EncryptorSigner class/interface, which can be
// instantiated with ethers' Signers and Wallets, but also with our custom
// classes
export class DelegatedSigner extends Wallet { // TODO: extend from generic Signer

authSignature?: AuthSignature;

async authenticate(selfDelegateProvider: SelfDelegateProvider){
const appSideSignerAddress = await this.getAddress();
this.authSignature = await selfDelegateProvider.getOrCreateAuthSignature(appSideSignerAddress);
}

override async signMessage(message: any): Promise<string> { // TODO: Restrict input type to Bytes | string
if (typeof this.authSignature === 'undefined'){
throw new Error('Encryptor must authenticate app signer first');
}
const appSignature = await super.signMessage(message);
return appSignature.concat(this.authSignature.signature)
}
}


export class SelfDelegateProvider {
private readonly storage: LocalStorage;

constructor(private readonly signer: ethers.Signer) {
this.storage = new LocalStorage();
}

public async createSelfDelegatedAppSideSigner(
ephemeralPrivateKey: any // TODO: Find a stricter type
): Promise<DelegatedSigner> {
const appSideSigner = new DelegatedSigner(ephemeralPrivateKey);
await appSideSigner.authenticate(this);
return appSideSigner;
}

public async getOrCreateAuthSignature(
ephemeralPublicKeyOrAddress: string
): Promise<AuthSignature> {
const address = await this.signer.getAddress();
const storageKey = `eth-${ENCRYPTOR_SELF_DELEGATE_AUTH_METHOD}-${address}-${ephemeralPublicKeyOrAddress}`;

// If we have a signature in localStorage, return it
const maybeSignature = this.storage.getAuthSignature(storageKey);
if (maybeSignature) {
// TODO: Consider design that includes freshness validation (see SIWE)
return maybeSignature;
}

// If at this point we didn't return, we need to create a new message
const authMessage = await this.createAuthMessage(ephemeralPublicKeyOrAddress);
this.storage.setAuthSignature(storageKey, authMessage);
return authMessage;
}

private async createAuthMessage(
ephemeralPublicKeyOrAddress: string
): Promise<AuthSignature> {
// TODO: Consider adding domain, uri, version, chainId (see SIWE signature)
const address = await this.signer.getAddress();
const scheme = ENCRYPTOR_SELF_DELEGATE_AUTH_METHOD;
const message = ephemeralPublicKeyOrAddress;
const signature = await this.signer.signMessage(message);
return { signature, address, scheme, typedData: message };
}
}
1 change: 1 addition & 0 deletions packages/taco-auth/src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './eip4361/eip4361';
export * from './eip4361/external-eip4361';
export * from './encryptor/self-delegate';
66 changes: 63 additions & 3 deletions packages/taco-auth/test/auth-provider.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
aliceSecretKeyBytes,
bobSecretKeyBytes,
fakeProvider,
TEST_SIWE_PARAMS,
Expand All @@ -8,11 +9,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import {
EIP4361AuthProvider,
SelfDelegateProvider,
SingleSignOnEIP4361AuthProvider,
} from '../src/providers';
import { EIP4361TypedDataSchema } from '../src/providers/eip4361/common';

describe('auth provider', () => {
describe('siwe auth provider', () => {
const provider = fakeProvider(bobSecretKeyBytes);
const signer = provider.getSigner();
const eip4361Provider = new EIP4361AuthProvider(
Expand Down Expand Up @@ -55,7 +57,7 @@ describe('auth provider', () => {
});
});

describe('auth provider caching', () => {
describe('siwe auth provider caching', () => {
beforeEach(() => {
// tell vitest we use mocked time
vi.useFakeTimers();
Expand Down Expand Up @@ -103,7 +105,7 @@ describe('auth provider caching', () => {
});
});

describe('single sign-on auth provider', async () => {
describe('single sign-on siwe auth provider', async () => {
const provider = fakeProvider(bobSecretKeyBytes);
const signer = provider.getSigner();

Expand Down Expand Up @@ -133,3 +135,61 @@ describe('single sign-on auth provider', async () => {
expect(typedSignature.scheme).toEqual('EIP4361');
});
});

describe('encryptor self-delegate provider authorization', () => {
const provider = fakeProvider(bobSecretKeyBytes);
const signer = provider.getSigner();
const selfDelegateProvider = new SelfDelegateProvider(signer);

it('creates a new self-delegated app signer', async () => {
const appSideSignerAddress = await applicationSideSigner.getAddress();
const [newSigner, newAuthSignature] = await selfDelegateProvider.createSelfDelegatedAppSideSigner(aliceSecretKeyBytes);
expect(await newSigner.getAddress()).toEqual(appSideSignerAddress);
expect(newAuthSignature.typedData).toEqual(appSideSignerAddress);
});

const applicationSideProvider = fakeProvider(aliceSecretKeyBytes);
const applicationSideSigner = applicationSideProvider.getSigner();

it('creates a new auth signature', async () => {
const appSideSignerAddress = await applicationSideSigner.getAddress();
const typedSignature = await selfDelegateProvider.getOrCreateAuthSignature(appSideSignerAddress);
expect(typedSignature.signature).toBeDefined();
expect(typedSignature.address).toEqual(await signer.getAddress());
expect(typedSignature.scheme).toEqual('EncryptorSelfDelegate');
expect(typedSignature.typedData).toEqual(appSideSignerAddress);

});

});

describe('encryptor self-delegate provider caching', () => {
const provider = fakeProvider(bobSecretKeyBytes);
const signer = provider.getSigner();
const selfDelegateProvider = new SelfDelegateProvider(signer);

const applicationSideProvider = fakeProvider(aliceSecretKeyBytes);
const applicationSideSigner = applicationSideProvider.getSigner();

it('caches signature', async () => {
const appSideSignerAddress = await applicationSideSigner.getAddress();

const createSignatureSpy = vi.spyOn(
selfDelegateProvider,
// @ts-expect-error -- spying on private function
'createAuthMessage',
);

expect(createSignatureSpy).toHaveBeenCalledTimes(0);

const typedSignature = await selfDelegateProvider.getOrCreateAuthSignature(appSideSignerAddress);
expect(createSignatureSpy).toHaveBeenCalledTimes(1);

const typedSignatureSecondCall =
await selfDelegateProvider.getOrCreateAuthSignature(appSideSignerAddress);
// auth signature is cached, so spy is not called a 2nd time
expect(createSignatureSpy).toHaveBeenCalledTimes(1);
expect(typedSignatureSecondCall).toEqual(typedSignature);
});

});
12 changes: 0 additions & 12 deletions packages/taco/src/taco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,6 @@ export const encrypt = async (
ritualId: number,
authSigner: ethers.Signer,
): Promise<ThresholdMessageKit> => {
// TODO(#264): Enable ritual initialization
// if (ritualId === undefined) {
// ritualId = await DkgClient.initializeRitual(
// provider,
// this.cohort.ursulaAddresses,
// true
// );
// }
// if (ritualId === undefined) {
// // Given that we just initialized the ritual, this should never happen
// throw new Error('Ritual ID is undefined');
// }
const dkgRitual = await DkgClient.getActiveRitual(provider, domain, ritualId);

return await encryptWithPublicKey(
Expand Down
Loading