Skip to content

Commit

Permalink
update coordinator contract
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Sep 6, 2023
1 parent 283c4b4 commit 9e589cc
Show file tree
Hide file tree
Showing 9 changed files with 954 additions and 123 deletions.
905 changes: 854 additions & 51 deletions abi/Coordinator.json

Large diffs are not rendered by default.

45 changes: 33 additions & 12 deletions src/agents/coordinator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SessionStaticKey } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import { DkgPublicKey, SessionStaticKey } from '@nucypher/nucypher-core';
import { BigNumberish, ethers } from 'ethers';

import {
Coordinator,
Expand All @@ -13,12 +13,16 @@ import { DEFAULT_WAIT_N_CONFIRMATIONS, getContract } from './contracts';

export interface CoordinatorRitual {
initiator: string;
dkgSize: number;
initTimestamp: number;
endTimestamp: number;
totalTranscripts: number;
totalAggregations: number;
publicKey: BLS12381.G1PointStructOutput;
authority: string;
dkgSize: number;
threshold: number;
aggregationMismatch: boolean;
accessController: string;
publicKey: BLS12381.G1PointStructOutput;
aggregatedTranscript: string;
}

Expand Down Expand Up @@ -59,10 +63,18 @@ export class DkgCoordinatorAgent {
public static async initializeRitual(
provider: ethers.providers.Provider,
signer: ethers.Signer,
providers: ChecksumAddress[]
providers: ChecksumAddress[],
authority: string,
duration: BigNumberish,
accessController: string
): Promise<number> {
const Coordinator = await this.connectReadWrite(provider, signer);
const tx = await Coordinator.initiateRitual(providers);
const tx = await Coordinator.initiateRitual(
providers,
authority,
duration,
accessController
);
const txReceipt = await tx.wait(DEFAULT_WAIT_N_CONFIRMATIONS);
const [ritualStartEvent] = txReceipt.events ?? [];
if (!ritualStartEvent) {
Expand Down Expand Up @@ -95,16 +107,25 @@ export class DkgCoordinatorAgent {
const Coordinator = await this.connectReadOnly(provider);
// We leave `initiator` undefined because we don't care who the initiator is
// We leave `successful` undefined because we don't care if the ritual was successful
const eventFilter = Coordinator.filters.EndRitual(
ritualId,
undefined,
undefined
);
Coordinator.once(eventFilter, (_ritualId, _initiator, successful) => {
const eventFilter = Coordinator.filters.EndRitual(ritualId, undefined);
Coordinator.once(eventFilter, (_ritualId, successful) => {
callback(successful);
});
}

public static async getRitualIdFromPublicKey(
provider: ethers.providers.Provider,
dkgPublicKey: DkgPublicKey
): Promise<number> {
const Coordinator = await this.connectReadOnly(provider);
const dkgPublicKeyBytes = dkgPublicKey.toBytes();
const pointStruct: BLS12381.G1PointStruct = {
word0: dkgPublicKeyBytes.slice(0, 16),
word1: dkgPublicKeyBytes.slice(16, 32),
};
return await Coordinator.getRitualIdFromPublicKey(pointStruct);
}

private static async connectReadOnly(provider: ethers.providers.Provider) {
return await this.connect(provider);
}
Expand Down
21 changes: 11 additions & 10 deletions src/dkg.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DkgPublicKey } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import { BigNumberish, ethers } from 'ethers';

import { DkgCoordinatorAgent, DkgRitualState } from './agents/coordinator';
import { ChecksumAddress } from './types';
Expand Down Expand Up @@ -58,22 +58,23 @@ export class DkgRitual {
}
}

// TODO: Currently, we're assuming that the threshold is always `floor(sharesNum / 2) + 1`.
// https://github.com/nucypher/nucypher/issues/3095
const assumedThreshold = (sharesNum: number): number =>
Math.floor(sharesNum / 2) + 1;

export class DkgClient {
public static async initializeRitual(
provider: ethers.providers.Provider,
signer: ethers.Signer,
ursulas: ChecksumAddress[],
authority: string,
duration: BigNumberish,
accessController: string,
waitUntilEnd = false
): Promise<number | undefined> {
const ritualId = await DkgCoordinatorAgent.initializeRitual(
provider,
signer,
ursulas.sort()
ursulas.sort(),
authority,
duration,
accessController
);

if (waitUntilEnd) {
Expand Down Expand Up @@ -111,7 +112,7 @@ export class DkgClient {
});
};

public static async getExistingRitual(
public static async getRitual(
provider: ethers.providers.Provider,
ritualId: number
): Promise<DkgRitual> {
Expand All @@ -129,7 +130,7 @@ export class DkgClient {
DkgPublicKey.fromBytes(dkgPkBytes),
{
sharesNum: ritual.dkgSize,
threshold: assumedThreshold(ritual.dkgSize),
threshold: ritual.threshold,
},
ritualState
);
Expand All @@ -139,7 +140,7 @@ export class DkgClient {
provider: ethers.providers.Provider,
ritualId: number
): Promise<DkgRitual> {
const ritual = await DkgClient.getExistingRitual(provider, ritualId);
const ritual = await DkgClient.getRitual(provider, ritualId);
if (ritual.state !== DkgRitualState.FINALIZED) {
throw new Error(
`Ritual ${ritualId} is not finalized. State: ${ritual.state}`
Expand Down
4 changes: 2 additions & 2 deletions src/sdk/strategy/cbd-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class CbdStrategy {
// // Given that we just initialized the ritual, this should never happen
// throw new Error('Ritual ID is undefined');
// }
const dkgRitual = await DkgClient.getExistingRitual(provider, ritualId);
const dkgRitual = await DkgClient.getRitual(provider, ritualId);
return DeployedCbdStrategy.create(dkgRitual, this.cohort.porterUri);
}

Expand Down Expand Up @@ -92,7 +92,7 @@ export class DeployedCbdStrategy {
porterUri: string,
ritualId: number
): Promise<DeployedCbdStrategy> {
const dkgRitual = await DkgClient.getExistingRitual(provider, ritualId);
const dkgRitual = await DkgClient.getRitual(provider, ritualId);
return DeployedCbdStrategy.create(dkgRitual, porterUri);
}

Expand Down
61 changes: 23 additions & 38 deletions src/taco.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,61 @@
import { DkgPublicKey, ThresholdMessageKit } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';

import { DkgCoordinatorAgent } from './agents/coordinator';
import { ThresholdDecrypter } from './characters/cbd-recipient';
import { Enrico } from './characters/enrico';
import { Condition, ConditionExpression } from './conditions';
import { DkgClient } from './dkg';
import { getPorterUri } from './porter';
import { toBytes } from './utils';

export interface TacoMessageKit {
thresholdMessageKit: ThresholdMessageKit;
conditionExpr: ConditionExpression;
// TODO: How do we get rid of these two fields? We need them for decrypting
// We ritualId in order to fetch the DKG participants and create DecryptionRequests for them
ritualId: number;
// We need to know the threshold in order to create DecryptionRequests
threshold: number;
}

export const encrypt = async (
provider: ethers.providers.Provider,
message: string,
condition: Condition,
ritualId: number
): Promise<TacoMessageKit> => {
): Promise<ThresholdMessageKit> => {
const dkgRitual = await DkgClient.getFinalizedRitual(provider, ritualId);
return await encryptLight(
message,
condition,
dkgRitual.dkgPublicKey,
dkgRitual.dkgParams.threshold,
ritualId
);
return await encryptLight(message, condition, dkgRitual.dkgPublicKey);
};

export const encryptLight = async (
message: string,
condition: Condition,
dkgPublicKey: DkgPublicKey,
// TODO: Remove these parameters after fixing TacoMessageKit
threshold: number,
ritualId: number
): Promise<TacoMessageKit> => {
dkgPublicKey: DkgPublicKey
): Promise<ThresholdMessageKit> => {
const encrypter = new Enrico(dkgPublicKey);
const conditionExpr = new ConditionExpression(condition);
const thresholdMessageKit = await encrypter.encryptMessageCbd(
toBytes(message),
conditionExpr
);
return {
thresholdMessageKit,
threshold,
ritualId,
conditionExpr,
};
return encrypter.encryptMessageCbd(toBytes(message), conditionExpr);
};

export const decrypt = async (
provider: ethers.providers.Provider,
messageKit: TacoMessageKit,
messageKit: ThresholdMessageKit,
signer?: ethers.Signer,
porterUri = getPorterUri('tapir')
): Promise<Uint8Array> => {
const ritualId = await DkgCoordinatorAgent.getRitualIdFromPublicKey(
provider,
messageKit.acp.publicKey
);
const ritual = await DkgClient.getFinalizedRitual(provider, ritualId);
const decrypter = ThresholdDecrypter.create(
porterUri,
messageKit.ritualId,
messageKit.threshold
ritualId,
ritual.dkgParams.threshold
);
// TODO: What do we do if there are no conditions?
if (!messageKit.acp.conditions) {
throw new Error('ThresholdMessageKit does not contain conditions');
}
const conditionExpr = ConditionExpression.fromWASMConditions(
messageKit.acp.conditions
);
return decrypter.retrieveAndDecrypt(
provider,
messageKit.conditionExpr,
messageKit.thresholdMessageKit,
conditionExpr,
messageKit,
signer
);
};
Expand Down
4 changes: 2 additions & 2 deletions test/unit/cbd-strategy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
fakeUrsulas,
makeCohort,
mockCbdDecrypt,
mockGetExistingRitual,
mockGetParticipants,
mockGetRitual,
mockGetUrsulas,
mockRandomSessionStaticSecret,
} from '../utils';
Expand Down Expand Up @@ -55,7 +55,7 @@ async function makeDeployedCbdStrategy() {
const mockedDkg = fakeDkgFlow(variant, 0, 4, 4);
const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const getUrsulasSpy = mockGetUrsulas(ursulas);
const getExistingRitualSpy = mockGetExistingRitual(mockedDkgRitual);
const getExistingRitualSpy = mockGetRitual(mockedDkgRitual);

const deployedStrategy = await strategy.deploy(provider, ritualId);

Expand Down
6 changes: 6 additions & 0 deletions test/unit/conditions/condition-expr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ describe('condition set', () => {
ConditionExpression.fromJSON(conditionExprJson);
expect(conditionExprFromJson).toBeDefined();
expect(conditionExprFromJson.equals(conditionExprFromJson)).toBeTruthy();

const asWasmConditions = conditionExprFromJson.toWASMConditions();
const fromWasmConditions =
ConditionExpression.fromWASMConditions(asWasmConditions);
expect(fromWasmConditions).toBeDefined();
expect(fromWasmConditions.equals(conditionExprFromJson)).toBeTruthy();
});

it('serializes to and from WASM conditions', () => {
Expand Down
19 changes: 13 additions & 6 deletions test/unit/taco.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
fakeSigner,
fakeTDecFlow,
mockCbdDecrypt,
mockGetExistingRitual,
mockGetParticipants,
mockGetRitual,
mockGetRitualIdFromPublicKey,
mockRandomSessionStaticSecret,
} from '../utils';

Expand All @@ -43,9 +44,9 @@ describe('taco', () => {
const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const provider = fakeProvider(aliceSecretKey.toBEBytes());
const signer = fakeSigner(aliceSecretKey.toBEBytes());
const getExistingRitualSpy = mockGetExistingRitual(mockedDkgRitual);
const getExistingRitualSpy = mockGetRitual(mockedDkgRitual);

const tacoMk = await taco.encrypt(
const messageKit = await taco.encrypt(
provider,
message,
ownsNFT,
Expand All @@ -57,9 +58,9 @@ describe('taco', () => {
const { decryptionShares } = fakeTDecFlow({
...mockedDkg,
message: toBytes(message),
conditionExpr: tacoMk.conditionExpr,
conditionExpr: new conditions.ConditionExpression(ownsNFT),
dkgPublicKey: mockedDkg.dkg.publicKey(),
thresholdMessageKit: tacoMk.thresholdMessageKit,
thresholdMessageKit: messageKit,
});
const { participantSecrets, participants } = fakeDkgParticipants(
mockedDkg.ritualId
Expand All @@ -73,15 +74,21 @@ describe('taco', () => {
);
const getParticipantsSpy = mockGetParticipants(participants);
const sessionKeySpy = mockRandomSessionStaticSecret(requesterSessionKey);
const getRitualIdFromPublicKey = mockGetRitualIdFromPublicKey(
mockedDkg.ritualId
);
const getRitualSpy = mockGetRitual(mockedDkgRitual);

const decryptedMessage = await taco.decrypt(
provider,
tacoMk,
messageKit,
signer,
fakePorterUri
);
expect(getParticipantsSpy).toHaveBeenCalled();
expect(sessionKeySpy).toHaveBeenCalled();
expect(getRitualIdFromPublicKey).toHaveBeenCalled();
expect(getRitualSpy).toHaveBeenCalled();
expect(decryptSpy).toHaveBeenCalled();
expect(decryptedMessage).toEqual(toBytes(message));
});
Expand Down
12 changes: 10 additions & 2 deletions test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,12 +526,20 @@ export const fakeDkgRitual = (ritual: {
);
};

export const mockGetExistingRitual = (dkgRitual: DkgRitual) => {
return jest.spyOn(DkgClient, 'getExistingRitual').mockImplementation(() => {
export const mockGetRitual = (dkgRitual: DkgRitual) => {
return jest.spyOn(DkgClient, 'getRitual').mockImplementation(() => {
return Promise.resolve(dkgRitual);
});
};

export const mockGetRitualIdFromPublicKey = (ritualId: number) => {
return jest
.spyOn(DkgCoordinatorAgent, 'getRitualIdFromPublicKey')
.mockImplementation(() => {
return Promise.resolve(ritualId);
});
};

export const makeCohort = async (ursulas: Ursula[]) => {
const getUrsulasSpy = mockGetUrsulas(ursulas);
const porterUri = 'https://_this.should.crash';
Expand Down

0 comments on commit 9e589cc

Please sign in to comment.