From 39241c0cacc2b29bf11a5f4c88a6d53bb8ea4375 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Thu, 29 Jun 2023 12:52:40 +0200
Subject: [PATCH 1/5] feat: implement local ritual verificaiton
---
src/agents/coordinator.ts | 32 +++++++++--
src/agents/subscription-manager.ts | 8 +--
src/characters/cbd-recipient.ts | 33 ++++++++++--
src/dkg.ts | 85 ++++++++++++++++++++----------
src/sdk/strategy/cbd-strategy.ts | 8 +--
test/unit/cbd-strategy.test.ts | 6 +++
test/utils.ts | 33 +++++++++---
7 files changed, 157 insertions(+), 48 deletions(-)
diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts
index 264e4ea50..c0b1a9d19 100644
--- a/src/agents/coordinator.ts
+++ b/src/agents/coordinator.ts
@@ -1,4 +1,4 @@
-import { SessionStaticKey } from '@nucypher/nucypher-core';
+import { SessionStaticKey, Transcript } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import {
@@ -24,12 +24,23 @@ export interface CoordinatorRitual {
export type DkgParticipant = {
provider: string;
aggregated: boolean;
+ // TODO: How do I get the transcript from the Coordinator?
+ transcript: Transcript;
decryptionRequestStaticKey: SessionStaticKey;
};
+export enum DkgRitualState {
+ NON_INITIATED,
+ AWAITING_TRANSCRIPTS,
+ AWAITING_AGGREGATIONS,
+ TIMEOUT,
+ INVALID,
+ FINALIZED,
+}
+
export class DkgCoordinatorAgent {
public static async getParticipants(
- provider: ethers.providers.Provider,
+ provider: ethers.providers.Web3Provider,
ritualId: number
): Promise {
const Coordinator = await this.connectReadOnly(provider);
@@ -39,6 +50,7 @@ export class DkgCoordinatorAgent {
return {
provider: participant.provider,
aggregated: participant.aggregated,
+ transcript: Transcript.fromBytes(fromHexString(participant.transcript)),
decryptionRequestStaticKey: SessionStaticKey.fromBytes(
fromHexString(participant.decryptionRequestStaticKey)
),
@@ -47,19 +59,29 @@ export class DkgCoordinatorAgent {
}
public static async getRitual(
- provider: ethers.providers.Provider,
+ provider: ethers.providers.Web3Provider,
ritualId: number
): Promise {
const Coordinator = await this.connectReadOnly(provider);
return Coordinator.rituals(ritualId);
}
- private static async connectReadOnly(provider: ethers.providers.Provider) {
+ public static async getRitualState(
+ provider: ethers.providers.Web3Provider,
+ ritualId: number
+ ): Promise {
+ const Coordinator = await this.connectReadOnly(provider);
+ return await Coordinator.getRitualState(ritualId);
+ }
+
+ private static async connectReadOnly(
+ provider: ethers.providers.Web3Provider
+ ) {
return await this.connect(provider);
}
private static async connect(
- provider: ethers.providers.Provider,
+ provider: ethers.providers.Web3Provider,
signer?: ethers.providers.JsonRpcSigner
): Promise {
const network = await provider.getNetwork();
diff --git a/src/agents/subscription-manager.ts b/src/agents/subscription-manager.ts
index ac8a5a3c5..f997508c8 100644
--- a/src/agents/subscription-manager.ts
+++ b/src/agents/subscription-manager.ts
@@ -48,7 +48,7 @@ export class PreSubscriptionManagerAgent {
}
public static async getPolicyCost(
- provider: ethers.providers.Provider,
+ provider: ethers.providers.Web3Provider,
size: number,
startTimestamp: number,
endTimestamp: number
@@ -61,7 +61,9 @@ export class PreSubscriptionManagerAgent {
);
}
- private static async connectReadOnly(provider: ethers.providers.Provider) {
+ private static async connectReadOnly(
+ provider: ethers.providers.Web3Provider
+ ) {
return await this.connect(provider);
}
@@ -72,7 +74,7 @@ export class PreSubscriptionManagerAgent {
}
private static async connect(
- provider: ethers.providers.Provider,
+ provider: ethers.providers.Web3Provider,
signer?: ethers.providers.JsonRpcSigner
): Promise {
const network = await provider.getNetwork();
diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts
index cef8d360c..6e69253d3 100644
--- a/src/characters/cbd-recipient.ts
+++ b/src/characters/cbd-recipient.ts
@@ -12,9 +12,14 @@ import {
} from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
-import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator';
+import {
+ DkgCoordinatorAgent,
+ DkgParticipant,
+ DkgRitualState,
+} from '../agents/coordinator';
import { ConditionExpression } from '../conditions';
import {
+ DkgClient,
DkgRitual,
FerveoVariant,
getCombineDecryptionSharesFunction,
@@ -73,16 +78,36 @@ export class CbdTDecDecrypter {
// Retrieve decryption shares
public async retrieve(
- provider: ethers.providers.Web3Provider,
+ web3Provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
variant: number,
ciphertext: Ciphertext
): Promise {
+ const ritualState = await DkgCoordinatorAgent.getRitualState(
+ web3Provider,
+ this.ritualId
+ );
+ if (ritualState !== DkgRitualState.FINALIZED) {
+ throw new Error(
+ `Ritual with id ${this.ritualId} is not finalized. Ritual state is ${ritualState}.`
+ );
+ }
+
+ const isLocallyVerified = await DkgClient.verifyRitual(
+ web3Provider,
+ this.ritualId
+ );
+ if (!isLocallyVerified) {
+ throw new Error(
+ `Ritual with id ${this.ritualId} has failed local verification.`
+ );
+ }
+
const dkgParticipants = await DkgCoordinatorAgent.getParticipants(
- provider,
+ web3Provider,
this.ritualId
);
- const contextStr = await conditionExpr.buildContext(provider).toJson();
+ const contextStr = await conditionExpr.buildContext(web3Provider).toJson();
const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests(
this.ritualId,
variant,
diff --git a/src/dkg.ts b/src/dkg.ts
index f0ee81e41..cdc038123 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -1,10 +1,15 @@
import {
+ AggregatedTranscript,
combineDecryptionSharesPrecomputed,
combineDecryptionSharesSimple,
DecryptionSharePrecomputed,
DecryptionShareSimple,
DkgPublicKey,
+ EthereumAddress,
+ FerveoPublicKey,
SharedSecret,
+ Validator,
+ ValidatorMessage,
} from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
@@ -84,16 +89,32 @@ export class DkgRitual {
}
}
-export class DkgClient {
- constructor(private readonly provider: ethers.providers.Web3Provider) {}
+// TODO: Without Validator public key in Coordinator, we cannot verify the
+// transcript. We need to add it to the Coordinator (nucypher-contracts #77).
+const participantPublicKeys: Record = {
+ '0x210eeAC07542F815ebB6FD6689637D8cA2689392': FerveoPublicKey.fromBytes(
+ fromHexString(
+ '6000000000000000ace9d7567b26dafc512b2303cfdaa872850c62b100078ddeaabf8408c7308b3a43dfeb88375c21ef63230fb4008ce7e908764463c6765e556f9b03009eb1757d179eaa26bf875332807cc070d62a385ed2e66e09f4f4766451da12779a09036e'
+ )
+ ),
+ '0xb15d5A4e2be34f4bE154A1b08a94Ab920FfD8A41': FerveoPublicKey.fromBytes(
+ fromHexString(
+ '60000000000000008b373fdb6b43e9dca028bd603c2bf90f0e008ec83ff217a8d7bc006b585570e6ab1ce761bad0e21c1aed1363286145f61134ed0ab53f4ebaa05036396c57f6e587f33d49667c1003cd03b71ad651b09dd4791bc631eaef93f1b313bbee7bd63a'
+ )
+ ),
+};
+export class DkgClient {
// TODO: Update API: Replace with getExistingRitual and support ritualId in Strategy
- public async initializeRitual(ritualParams: {
- shares: number;
- threshold: number;
- }): Promise {
+ public static async initializeRitual(
+ web3Provider: ethers.providers.Web3Provider,
+ ritualParams: {
+ shares: number;
+ threshold: number;
+ }
+ ): Promise {
const ritualId = 2;
- const ritual = await DkgCoordinatorAgent.getRitual(this.provider, ritualId);
+ const ritual = await DkgCoordinatorAgent.getRitual(web3Provider, ritualId);
const dkgPkBytes = new Uint8Array([
...fromHexString(ritual.publicKey.word0),
...fromHexString(ritual.publicKey.word1),
@@ -106,24 +127,34 @@ export class DkgClient {
} as DkgRitual;
}
- // TODO: Without Validator public key in Coordinator, we cannot verify the
- // transcript. We need to add it to the Coordinator (nucypher-contracts #77).
- // public async verifyRitual(ritualId: number): Promise {
- // const ritual = await DkgCoordinatorAgent.getRitual(this.provider, ritualId);
- // const participants = await DkgCoordinatorAgent.getParticipants(
- // this.provider,
- // ritualId
- // );
- //
- // const validatorMessages = participants.map((p) => {
- // const validatorAddress = EthereumAddress.fromString(p.provider);
- // const publicKey = FerveoPublicKey.fromBytes(fromHexString(p.???));
- // const validator = new Validator(validatorAddress, publicKey);
- // const transcript = Transcript.fromBytes(fromHexString(p.transcript));
- // return new ValidatorMessage(validator, transcript);
- // });
- // const aggregate = new AggregatedTranscript(validatorMessages);
- //
- // return aggregate.verify(ritual.dkgSize, validatorMessages);
- // }
+ public static async verifyRitual(
+ web3Provider: ethers.providers.Web3Provider,
+ ritualId: number
+ ): Promise {
+ const ritual = await DkgCoordinatorAgent.getRitual(web3Provider, ritualId);
+ const participants = await DkgCoordinatorAgent.getParticipants(
+ web3Provider,
+ ritualId
+ );
+
+ // TODO: Does this check make sense here? Or do we delegate it to the Coordinator contract?
+ // for (const p of participants) {
+ // // Not every participant has submitted a transcript
+ // if (!p.aggregated) {
+ // return false;
+ // }
+ // }
+
+ const validatorMessages = participants.map((p) => {
+ const validatorAddress = EthereumAddress.fromString(p.provider);
+ // TODO: Replace with real keys
+ // const publicKey = FerveoPublicKey.fromBytes(fromHexString(p.???));
+ const publicKey = participantPublicKeys[p.provider];
+ const validator = new Validator(validatorAddress, publicKey);
+ return new ValidatorMessage(validator, p.transcript);
+ });
+ const aggregate = new AggregatedTranscript(validatorMessages);
+
+ return aggregate.verify(ritual.dkgSize, validatorMessages);
+ }
}
diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts
index 0632eeaef..87d424348 100644
--- a/src/sdk/strategy/cbd-strategy.ts
+++ b/src/sdk/strategy/cbd-strategy.ts
@@ -30,14 +30,16 @@ export class CbdStrategy {
}
public async deploy(
- provider: ethers.providers.Web3Provider
+ web3Provider: ethers.providers.Web3Provider
): Promise {
const dkgRitualParams = {
threshold: this.cohort.configuration.threshold,
shares: this.cohort.configuration.shares,
};
- const dkgClient = new DkgClient(provider);
- const dkgRitual = await dkgClient.initializeRitual(dkgRitualParams);
+ const dkgRitual = await DkgClient.initializeRitual(
+ web3Provider,
+ dkgRitualParams
+ );
return DeployedCbdStrategy.create(this.cohort, dkgRitual);
}
diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts
index 36a8f216b..801379ae1 100644
--- a/test/unit/cbd-strategy.test.ts
+++ b/test/unit/cbd-strategy.test.ts
@@ -15,9 +15,11 @@ import {
makeCohort,
mockCbdDecrypt,
mockGetParticipants,
+ mockGetRitualState,
mockGetUrsulas,
mockInitializeRitual,
mockRandomSessionStaticSecret,
+ mockVerifyRitual,
} from '../utils';
import { aliceSecretKeyBytes } from './testVariables';
@@ -127,6 +129,8 @@ describe('CbdDeployedStrategy', () => {
const getParticipantsSpy = mockGetParticipants(participants);
const getUrsulasSpy = mockGetUrsulas(ursulas);
const sessionKeySpy = mockRandomSessionStaticSecret(requesterSessionKey);
+ const getRitualStateSpy = mockGetRitualState();
+ const verifyRitualSpy = mockVerifyRitual();
const decryptedMessage =
await deployedStrategy.decrypter.retrieveAndDecrypt(
@@ -135,6 +139,8 @@ describe('CbdDeployedStrategy', () => {
variant,
ciphertext
);
+ expect(getRitualStateSpy).toHaveBeenCalled();
+ expect(verifyRitualSpy).toHaveBeenCalled();
expect(getUrsulasSpy).toHaveBeenCalled();
expect(getParticipantsSpy).toHaveBeenCalled();
expect(sessionKeySpy).toHaveBeenCalled();
diff --git a/test/utils.ts b/test/utils.ts
index 46dce7df5..5fb64db51 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -38,7 +38,11 @@ import { ethers, providers, Wallet } from 'ethers';
import { keccak256 } from 'ethers/lib/utils';
import { Alice, Bob, Cohort, Configuration, RemoteBob } from '../src';
-import { DkgCoordinatorAgent, DkgParticipant } from '../src/agents/coordinator';
+import {
+ DkgCoordinatorAgent,
+ DkgParticipant,
+ DkgRitualState,
+} from '../src/agents/coordinator';
import { CbdTDecDecrypter } from '../src/characters/cbd-recipient';
import {
CbdDecryptResult,
@@ -501,11 +505,14 @@ export const fakeDkgRitual = (ritual: { dkg: Dkg }, threshold: number) => {
};
export const mockInitializeRitual = (fakeRitual: unknown) => {
- return jest
- .spyOn(DkgClient.prototype as any, 'initializeRitual')
- .mockImplementation(() => {
- return Promise.resolve(fakeRitual);
- });
+ return (
+ jest
+ .spyOn(DkgClient, 'initializeRitual')
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ .mockImplementation((_web3Provider, _ritualParams) => {
+ return Promise.resolve(fakeRitual) as Promise;
+ })
+ );
};
export const makeCohort = async (ursulas: Ursula[]) => {
@@ -519,3 +526,17 @@ export const makeCohort = async (ursulas: Ursula[]) => {
expect(getUrsulasSpy).toHaveBeenCalled();
return cohort;
};
+
+export const mockGetRitualState = (state = DkgRitualState.FINALIZED) => {
+ return jest.spyOn(DkgCoordinatorAgent, 'getRitualState').mockImplementation(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ (_provider, _ritualId) => Promise.resolve(state)
+ );
+};
+
+export const mockVerifyRitual = (isValid = true) => {
+ return jest.spyOn(DkgClient, 'verifyRitual').mockImplementation(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ (_provider, _ritualId) => Promise.resolve(isValid)
+ );
+};
From e241bb7e3c193bc09bf2f0d9eb90fefcc4d326a3 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Thu, 29 Jun 2023 15:15:44 +0200
Subject: [PATCH 2/5] test local verification
---
src/dkg.ts | 40 +++++++++++++---------
test/integration/dkg-client.test.ts | 52 +++++++++++++++++++++++------
test/utils.ts | 12 +++++++
3 files changed, 77 insertions(+), 27 deletions(-)
diff --git a/src/dkg.ts b/src/dkg.ts
index cdc038123..8d10d97ef 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -89,21 +89,6 @@ export class DkgRitual {
}
}
-// TODO: Without Validator public key in Coordinator, we cannot verify the
-// transcript. We need to add it to the Coordinator (nucypher-contracts #77).
-const participantPublicKeys: Record = {
- '0x210eeAC07542F815ebB6FD6689637D8cA2689392': FerveoPublicKey.fromBytes(
- fromHexString(
- '6000000000000000ace9d7567b26dafc512b2303cfdaa872850c62b100078ddeaabf8408c7308b3a43dfeb88375c21ef63230fb4008ce7e908764463c6765e556f9b03009eb1757d179eaa26bf875332807cc070d62a385ed2e66e09f4f4766451da12779a09036e'
- )
- ),
- '0xb15d5A4e2be34f4bE154A1b08a94Ab920FfD8A41': FerveoPublicKey.fromBytes(
- fromHexString(
- '60000000000000008b373fdb6b43e9dca028bd603c2bf90f0e008ec83ff217a8d7bc006b585570e6ab1ce761bad0e21c1aed1363286145f61134ed0ab53f4ebaa05036396c57f6e587f33d49667c1003cd03b71ad651b09dd4791bc631eaef93f1b313bbee7bd63a'
- )
- ),
-};
-
export class DkgClient {
// TODO: Update API: Replace with getExistingRitual and support ritualId in Strategy
public static async initializeRitual(
@@ -149,7 +134,7 @@ export class DkgClient {
const validatorAddress = EthereumAddress.fromString(p.provider);
// TODO: Replace with real keys
// const publicKey = FerveoPublicKey.fromBytes(fromHexString(p.???));
- const publicKey = participantPublicKeys[p.provider];
+ const publicKey = DkgClient.getParticipantPublicKey(p.provider);
const validator = new Validator(validatorAddress, publicKey);
return new ValidatorMessage(validator, p.transcript);
});
@@ -157,4 +142,27 @@ export class DkgClient {
return aggregate.verify(ritual.dkgSize, validatorMessages);
}
+
+ public static getParticipantPublicKey = (address: string) => {
+ // TODO: Without Validator public key in Coordinator, we cannot verify the
+ // transcript. We need to add it to the Coordinator (nucypher-contracts #77).
+ const participantPublicKeys: Record = {
+ '0x210eeAC07542F815ebB6FD6689637D8cA2689392': FerveoPublicKey.fromBytes(
+ fromHexString(
+ '6000000000000000ace9d7567b26dafc512b2303cfdaa872850c62b100078ddeaabf8408c7308b3a43dfeb88375c21ef63230fb4008ce7e908764463c6765e556f9b03009eb1757d179eaa26bf875332807cc070d62a385ed2e66e09f4f4766451da12779a09036e'
+ )
+ ),
+ '0xb15d5A4e2be34f4bE154A1b08a94Ab920FfD8A41': FerveoPublicKey.fromBytes(
+ fromHexString(
+ '60000000000000008b373fdb6b43e9dca028bd603c2bf90f0e008ec83ff217a8d7bc006b585570e6ab1ce761bad0e21c1aed1363286145f61134ed0ab53f4ebaa05036396c57f6e587f33d49667c1003cd03b71ad651b09dd4791bc631eaef93f1b313bbee7bd63a'
+ )
+ ),
+ };
+
+ const publicKey = participantPublicKeys[address];
+ if (!publicKey) {
+ throw new Error(`No public key for participant: ${address}`);
+ }
+ return publicKey;
+ };
}
diff --git a/test/integration/dkg-client.test.ts b/test/integration/dkg-client.test.ts
index acb8337b6..ff2150465 100644
--- a/test/integration/dkg-client.test.ts
+++ b/test/integration/dkg-client.test.ts
@@ -1,18 +1,22 @@
import { SecretKey } from '@nucypher/nucypher-core';
import { DkgCoordinatorAgent } from '../../src/agents/coordinator';
+import { DkgClient } from '../../src/dkg';
import {
fakeCoordinatorRitual,
fakeDkgParticipants,
fakeRitualId,
fakeWeb3Provider,
+ mockGetParticipantPublicKey,
mockGetParticipants,
+ mockVerifyRitual,
} from '../utils';
jest.mock('../../src/agents/coordinator', () => ({
DkgCoordinatorAgent: {
getRitual: () => Promise.resolve(fakeCoordinatorRitual(fakeRitualId)),
- getParticipants: () => Promise.resolve(fakeDkgParticipants(fakeRitualId)),
+ getParticipants: () =>
+ Promise.resolve(fakeDkgParticipants(fakeRitualId).participants),
},
}));
@@ -42,13 +46,39 @@ describe('DkgCoordinatorAgent', () => {
});
});
-// TODO: Fix this test after the DkgClient.verifyRitual() method is implemented
-// describe('DkgClient', () => {
-// it('verifies the dkg ritual', async () => {
-// const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
-//
-// const dkgClient = new DkgClient(provider);
-// const isValid = await dkgClient.verifyRitual(fakeRitualId);
-// expect(isValid).toBeTruthy();
-// });
-// });
+describe('DkgClient', () => {
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('verifies the dkg ritual', async () => {
+ const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
+ const verifyRitualSpy = mockVerifyRitual();
+
+ const isValid = await DkgClient.verifyRitual(provider, fakeRitualId);
+ expect(isValid).toBeTruthy();
+ expect(verifyRitualSpy).toHaveBeenCalled();
+ });
+
+ it('rejects on missing participant pk', async () => {
+ const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
+
+ await expect(async () =>
+ DkgClient.verifyRitual(provider, fakeRitualId)
+ ).rejects.toThrow(
+ 'No public key for participant: 0x0000000000000000000000000000000000000000'
+ );
+ });
+
+ it('rejects on bad participant pk', async () => {
+ const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
+ const getParticipantPublicKeysSpy = mockGetParticipantPublicKey();
+
+ await expect(async () =>
+ DkgClient.verifyRitual(provider, fakeRitualId)
+ ).rejects.toThrow(
+ "Transcript aggregate doesn't match the received PVSS instances"
+ );
+ expect(getParticipantPublicKeysSpy).toHaveBeenCalled();
+ });
+});
diff --git a/test/utils.ts b/test/utils.ts
index 5fb64db51..c84da1f18 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -8,6 +8,7 @@ import {
EncryptedThresholdDecryptionResponse,
EncryptedTreasureMap,
ferveoEncrypt,
+ FerveoPublicKey,
PublicKey,
reencrypt,
SecretKey,
@@ -540,3 +541,14 @@ export const mockVerifyRitual = (isValid = true) => {
(_provider, _ritualId) => Promise.resolve(isValid)
);
};
+
+export const mockGetParticipantPublicKey = (pk = fakeFerveoPublicKey()) => {
+ return jest.spyOn(DkgClient, 'getParticipantPublicKey').mockImplementation(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ (_address) => pk
+ );
+};
+
+export const fakeFerveoPublicKey = (): FerveoPublicKey => {
+ return Keypair.random().publicKey;
+};
From c54c38f5fa0253fd887c5b37801103c2fe837a8f Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Thu, 29 Jun 2023 18:02:22 +0200
Subject: [PATCH 3/5] apply pr suggestions
---
src/agents/coordinator.ts | 1 -
src/characters/cbd-recipient.ts | 25 +++++++++++++++----------
src/dkg.ts | 8 --------
3 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts
index c0b1a9d19..03c49b9e9 100644
--- a/src/agents/coordinator.ts
+++ b/src/agents/coordinator.ts
@@ -24,7 +24,6 @@ export interface CoordinatorRitual {
export type DkgParticipant = {
provider: string;
aggregated: boolean;
- // TODO: How do I get the transcript from the Coordinator?
transcript: Transcript;
decryptionRequestStaticKey: SessionStaticKey;
};
diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts
index 6e69253d3..8280eb0c8 100644
--- a/src/characters/cbd-recipient.ts
+++ b/src/characters/cbd-recipient.ts
@@ -57,13 +57,15 @@ export class CbdTDecDecrypter {
provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
variant: FerveoVariant,
- ciphertext: Ciphertext
+ ciphertext: Ciphertext,
+ verifyRitual = true
): Promise {
const decryptionShares = await this.retrieve(
provider,
conditionExpr,
variant,
- ciphertext
+ ciphertext,
+ verifyRitual
);
const combineDecryptionSharesFn =
@@ -81,7 +83,8 @@ export class CbdTDecDecrypter {
web3Provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
variant: number,
- ciphertext: Ciphertext
+ ciphertext: Ciphertext,
+ verifyRitual = true
): Promise {
const ritualState = await DkgCoordinatorAgent.getRitualState(
web3Provider,
@@ -93,14 +96,16 @@ export class CbdTDecDecrypter {
);
}
- const isLocallyVerified = await DkgClient.verifyRitual(
- web3Provider,
- this.ritualId
- );
- if (!isLocallyVerified) {
- throw new Error(
- `Ritual with id ${this.ritualId} has failed local verification.`
+ if (verifyRitual) {
+ const isLocallyVerified = await DkgClient.verifyRitual(
+ web3Provider,
+ this.ritualId
);
+ if (!isLocallyVerified) {
+ throw new Error(
+ `Ritual with id ${this.ritualId} has failed local verification.`
+ );
+ }
}
const dkgParticipants = await DkgCoordinatorAgent.getParticipants(
diff --git a/src/dkg.ts b/src/dkg.ts
index 8d10d97ef..9108d105b 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -122,14 +122,6 @@ export class DkgClient {
ritualId
);
- // TODO: Does this check make sense here? Or do we delegate it to the Coordinator contract?
- // for (const p of participants) {
- // // Not every participant has submitted a transcript
- // if (!p.aggregated) {
- // return false;
- // }
- // }
-
const validatorMessages = participants.map((p) => {
const validatorAddress = EthereumAddress.fromString(p.provider);
// TODO: Replace with real keys
From 7f54da40aab17a1258ab53e8253fb95a08ebce30 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 30 Jun 2023 13:15:54 +0200
Subject: [PATCH 4/5] feat! add ritual initialization
---
src/agents/coordinator.ts | 41 +++++++++++-
src/characters/cbd-recipient.ts | 2 +-
src/dkg.ts | 106 +++++++++++++++++++++++++------
src/sdk/strategy/cbd-strategy.ts | 6 +-
test/unit/cbd-strategy.test.ts | 2 +-
test/utils.ts | 79 ++++++++++++-----------
6 files changed, 171 insertions(+), 65 deletions(-)
diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts
index 03c49b9e9..ccab3cf26 100644
--- a/src/agents/coordinator.ts
+++ b/src/agents/coordinator.ts
@@ -6,9 +6,10 @@ import {
Coordinator__factory,
} from '../../types/ethers-contracts';
import { BLS12381 } from '../../types/ethers-contracts/Coordinator';
+import { ChecksumAddress } from '../types';
import { fromHexString } from '../utils';
-import { getContract } from './contracts';
+import { DEFAULT_WAIT_N_CONFIRMATIONS, getContract } from './contracts';
export interface CoordinatorRitual {
initiator: string;
@@ -57,6 +58,20 @@ export class DkgCoordinatorAgent {
});
}
+ public static async initializeRitual(
+ provider: ethers.providers.Web3Provider,
+ providers: ChecksumAddress[]
+ ): Promise {
+ const Coordinator = await this.connectReadWrite(provider);
+ const tx = await Coordinator.initiateRitual(providers);
+ const txReceipt = await tx.wait(DEFAULT_WAIT_N_CONFIRMATIONS);
+ const [ritualStartEvent] = txReceipt.events ?? [];
+ if (!ritualStartEvent) {
+ throw new Error('Ritual start event not found');
+ }
+ return ritualStartEvent.args?.ritualId.toNumber();
+ }
+
public static async getRitual(
provider: ethers.providers.Web3Provider,
ritualId: number
@@ -73,12 +88,36 @@ export class DkgCoordinatorAgent {
return await Coordinator.getRitualState(ritualId);
}
+ public static async onRitualEndEvent(
+ provider: ethers.providers.Web3Provider,
+ ritualId: number,
+ callback: (successful: boolean) => void
+ ): Promise {
+ 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) => {
+ callback(successful);
+ });
+ }
+
private static async connectReadOnly(
provider: ethers.providers.Web3Provider
) {
return await this.connect(provider);
}
+ private static async connectReadWrite(
+ web3Provider: ethers.providers.Web3Provider
+ ) {
+ return await this.connect(web3Provider, web3Provider.getSigner());
+ }
+
private static async connect(
provider: ethers.providers.Web3Provider,
signer?: ethers.providers.JsonRpcSigner
diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts
index 8280eb0c8..89e96c271 100644
--- a/src/characters/cbd-recipient.ts
+++ b/src/characters/cbd-recipient.ts
@@ -48,7 +48,7 @@ export class CbdTDecDecrypter {
return new CbdTDecDecrypter(
new Porter(porterUri),
dkgRitual.id,
- dkgRitual.threshold
+ dkgRitual.dkgParams.threshold
);
}
diff --git a/src/dkg.ts b/src/dkg.ts
index 9108d105b..5de7ee4cd 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -13,8 +13,9 @@ import {
} from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
-import { DkgCoordinatorAgent } from './agents/coordinator';
-import { bytesEquals, fromHexString } from './utils';
+import { DkgCoordinatorAgent, DkgRitualState } from './agents/coordinator';
+import { ChecksumAddress } from './types';
+import { bytesEquals, fromHexString, objectEquals } from './utils';
// TODO: Expose from @nucypher/nucypher-core
export enum FerveoVariant {
@@ -50,33 +51,47 @@ export function getCombineDecryptionSharesFunction(
}
}
+export type DkgRitualParameters = {
+ sharesNum: number;
+ threshold: number;
+};
+
export interface DkgRitualJSON {
id: number;
dkgPublicKey: Uint8Array;
- threshold: number;
+ dkgParams: DkgRitualParameters;
+ state: DkgRitualState;
}
export class DkgRitual {
constructor(
public readonly id: number,
public readonly dkgPublicKey: DkgPublicKey,
- public readonly threshold: number
+ public readonly dkgParams: DkgRitualParameters,
+ public readonly state: DkgRitualState
) {}
public toObj(): DkgRitualJSON {
return {
id: this.id,
dkgPublicKey: this.dkgPublicKey.toBytes(),
- threshold: this.threshold,
+ dkgParams: this.dkgParams,
+ state: this.state,
};
}
public static fromObj({
id,
dkgPublicKey,
- threshold,
+ dkgParams,
+ state,
}: DkgRitualJSON): DkgRitual {
- return new DkgRitual(id, DkgPublicKey.fromBytes(dkgPublicKey), threshold);
+ return new DkgRitual(
+ id,
+ DkgPublicKey.fromBytes(dkgPublicKey),
+ dkgParams,
+ state
+ );
}
public equals(other: DkgRitual): boolean {
@@ -84,32 +99,85 @@ export class DkgRitual {
this.id === other.id &&
// TODO: Replace with `equals` after https://github.com/nucypher/nucypher-core/issues/56 is fixed
bytesEquals(this.dkgPublicKey.toBytes(), other.dkgPublicKey.toBytes()) &&
- this.threshold === other.threshold
+ objectEquals(this.dkgParams, other.dkgParams) &&
+ this.state === other.state
);
}
}
+// 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 {
- // TODO: Update API: Replace with getExistingRitual and support ritualId in Strategy
public static async initializeRitual(
web3Provider: ethers.providers.Web3Provider,
- ritualParams: {
- shares: number;
- threshold: number;
+ ursulas: ChecksumAddress[],
+ waitUntilEnd = false
+ ): Promise {
+ const ritualId = await DkgCoordinatorAgent.initializeRitual(
+ web3Provider,
+ ursulas
+ );
+
+ if (waitUntilEnd) {
+ const isSuccessful = await DkgClient.waitUntilRitualEnd(
+ web3Provider,
+ ritualId
+ );
+ if (!isSuccessful) {
+ const ritualState = await DkgCoordinatorAgent.getRitualState(
+ web3Provider,
+ ritualId
+ );
+ throw new Error(
+ `Ritual initialization failed. Ritual id ${ritualId} is in state ${ritualState}`
+ );
+ }
}
+
+ return this.getExistingRitual(web3Provider, ritualId);
+ }
+
+ private static waitUntilRitualEnd = async (
+ web3Provider: ethers.providers.Web3Provider,
+ ritualId: number
+ ): Promise => {
+ return new Promise((resolve, reject) => {
+ const callback = (successful: boolean) => {
+ if (successful) {
+ resolve(true);
+ } else {
+ reject();
+ }
+ };
+ DkgCoordinatorAgent.onRitualEndEvent(web3Provider, ritualId, callback);
+ });
+ };
+
+ public static async getExistingRitual(
+ web3Provider: ethers.providers.Web3Provider,
+ ritualId: number
): Promise {
- const ritualId = 2;
+ const ritualState = await DkgCoordinatorAgent.getRitualState(
+ web3Provider,
+ ritualId
+ );
const ritual = await DkgCoordinatorAgent.getRitual(web3Provider, ritualId);
const dkgPkBytes = new Uint8Array([
...fromHexString(ritual.publicKey.word0),
...fromHexString(ritual.publicKey.word1),
]);
-
- return {
- id: ritualId,
- dkgPublicKey: DkgPublicKey.fromBytes(dkgPkBytes),
- threshold: ritualParams.threshold,
- } as DkgRitual;
+ return new DkgRitual(
+ ritualId,
+ DkgPublicKey.fromBytes(dkgPkBytes),
+ {
+ sharesNum: ritual.dkgSize,
+ threshold: assumedThreshold(ritual.dkgSize),
+ },
+ ritualState
+ );
}
public static async verifyRitual(
diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts
index 87d424348..2b6887371 100644
--- a/src/sdk/strategy/cbd-strategy.ts
+++ b/src/sdk/strategy/cbd-strategy.ts
@@ -32,13 +32,9 @@ export class CbdStrategy {
public async deploy(
web3Provider: ethers.providers.Web3Provider
): Promise {
- const dkgRitualParams = {
- threshold: this.cohort.configuration.threshold,
- shares: this.cohort.configuration.shares,
- };
const dkgRitual = await DkgClient.initializeRitual(
web3Provider,
- dkgRitualParams
+ this.cohort.ursulaAddresses
);
return DeployedCbdStrategy.create(this.cohort, dkgRitual);
}
diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts
index 801379ae1..9723b328d 100644
--- a/test/unit/cbd-strategy.test.ts
+++ b/test/unit/cbd-strategy.test.ts
@@ -52,7 +52,7 @@ async function makeDeployedCbdStrategy() {
const strategy = await makeCbdStrategy();
const mockedDkg = fakeDkgFlow(variant, 0, 4, 4);
- const mockedDkgRitual = fakeDkgRitual(mockedDkg, mockedDkg.threshold);
+ const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const web3Provider = fakeWeb3Provider(aliceSecretKey.toBEBytes());
const getUrsulasSpy = mockGetUrsulas(ursulas);
const initializeRitualSpy = mockInitializeRitual(mockedDkgRitual);
diff --git a/test/utils.ts b/test/utils.ts
index c84da1f18..4ffaa1ba2 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -1,14 +1,25 @@
-// Disabling some of the eslint rules for conveninence.
+// Disabling some of the eslint rules for convenience.
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/no-unused-vars */
import { Block } from '@ethersproject/providers';
import {
+ AggregatedTranscript,
Capsule,
CapsuleFrag,
+ Ciphertext,
+ combineDecryptionSharesPrecomputed,
+ combineDecryptionSharesSimple,
+ DecryptionSharePrecomputed,
+ DecryptionShareSimple,
+ decryptWithSharedSecret,
+ Dkg,
EncryptedThresholdDecryptionResponse,
EncryptedTreasureMap,
+ EthereumAddress,
ferveoEncrypt,
FerveoPublicKey,
+ Keypair,
PublicKey,
reencrypt,
SecretKey,
@@ -16,23 +27,11 @@ import {
SessionStaticKey,
SessionStaticSecret,
ThresholdDecryptionResponse,
- VerifiedCapsuleFrag,
- VerifiedKeyFrag,
-} from '@nucypher/nucypher-core';
-import {
- AggregatedTranscript,
- Ciphertext,
- combineDecryptionSharesPrecomputed,
- combineDecryptionSharesSimple,
- DecryptionSharePrecomputed,
- DecryptionShareSimple,
- decryptWithSharedSecret,
- Dkg,
- EthereumAddress,
- Keypair,
Transcript,
Validator,
ValidatorMessage,
+ VerifiedCapsuleFrag,
+ VerifiedKeyFrag,
} from '@nucypher/nucypher-core';
import axios from 'axios';
import { ethers, providers, Wallet } from 'ethers';
@@ -501,19 +500,26 @@ export const mockRandomSessionStaticSecret = (secret: SessionStaticSecret) => {
export const fakeRitualId = 0;
-export const fakeDkgRitual = (ritual: { dkg: Dkg }, threshold: number) => {
- return new DkgRitual(fakeRitualId, ritual.dkg.publicKey(), threshold);
+export const fakeDkgRitual = (ritual: {
+ dkg: Dkg;
+ sharesNum: number;
+ threshold: number;
+}) => {
+ return new DkgRitual(
+ fakeRitualId,
+ ritual.dkg.publicKey(),
+ {
+ sharesNum: ritual.sharesNum,
+ threshold: ritual.threshold,
+ },
+ DkgRitualState.FINALIZED
+ );
};
-export const mockInitializeRitual = (fakeRitual: unknown) => {
- return (
- jest
- .spyOn(DkgClient, 'initializeRitual')
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- .mockImplementation((_web3Provider, _ritualParams) => {
- return Promise.resolve(fakeRitual) as Promise;
- })
- );
+export const mockInitializeRitual = (dkgRitual: DkgRitual) => {
+ return jest.spyOn(DkgClient, 'initializeRitual').mockImplementation(() => {
+ return Promise.resolve(dkgRitual);
+ });
};
export const makeCohort = async (ursulas: Ursula[]) => {
@@ -529,24 +535,21 @@ export const makeCohort = async (ursulas: Ursula[]) => {
};
export const mockGetRitualState = (state = DkgRitualState.FINALIZED) => {
- return jest.spyOn(DkgCoordinatorAgent, 'getRitualState').mockImplementation(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- (_provider, _ritualId) => Promise.resolve(state)
- );
+ return jest
+ .spyOn(DkgCoordinatorAgent, 'getRitualState')
+ .mockImplementation((_provider, _ritualId) => Promise.resolve(state));
};
export const mockVerifyRitual = (isValid = true) => {
- return jest.spyOn(DkgClient, 'verifyRitual').mockImplementation(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- (_provider, _ritualId) => Promise.resolve(isValid)
- );
+ return jest
+ .spyOn(DkgClient, 'verifyRitual')
+ .mockImplementation((_provider, _ritualId) => Promise.resolve(isValid));
};
export const mockGetParticipantPublicKey = (pk = fakeFerveoPublicKey()) => {
- return jest.spyOn(DkgClient, 'getParticipantPublicKey').mockImplementation(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- (_address) => pk
- );
+ return jest
+ .spyOn(DkgClient, 'getParticipantPublicKey')
+ .mockImplementation((_address) => pk);
};
export const fakeFerveoPublicKey = (): FerveoPublicKey => {
From 2d1aa90d014fd4936340739432a175a98110344e Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 11 Jul 2023 15:37:48 +0200
Subject: [PATCH 5/5] add feedback from live testing
---
src/agents/coordinator.ts | 2 +-
src/dkg.ts | 6 +++---
src/policies/policy.ts | 5 +++++
src/sdk/cohort.ts | 9 +++++++++
src/sdk/strategy/cbd-strategy.ts | 19 ++++++++++++++-----
test/acceptance/alice-grants.test.ts | 2 +-
test/acceptance/delay-enact.test.ts | 2 +-
test/docs/cbd.test.ts | 4 ++--
test/integration/pre.test.ts | 2 +-
test/unit/cbd-strategy.test.ts | 8 ++++++--
test/unit/cohort.test.ts | 2 +-
test/unit/pre-strategy.test.ts | 2 +-
test/utils.ts | 17 ++++++++++++-----
13 files changed, 57 insertions(+), 23 deletions(-)
diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts
index ccab3cf26..ca1e41f17 100644
--- a/src/agents/coordinator.ts
+++ b/src/agents/coordinator.ts
@@ -69,7 +69,7 @@ export class DkgCoordinatorAgent {
if (!ritualStartEvent) {
throw new Error('Ritual start event not found');
}
- return ritualStartEvent.args?.ritualId.toNumber();
+ return ritualStartEvent.args?.ritualId;
}
public static async getRitual(
diff --git a/src/dkg.ts b/src/dkg.ts
index 5de7ee4cd..ac30ac39a 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -115,10 +115,10 @@ export class DkgClient {
web3Provider: ethers.providers.Web3Provider,
ursulas: ChecksumAddress[],
waitUntilEnd = false
- ): Promise {
+ ): Promise {
const ritualId = await DkgCoordinatorAgent.initializeRitual(
web3Provider,
- ursulas
+ ursulas.sort()
);
if (waitUntilEnd) {
@@ -137,7 +137,7 @@ export class DkgClient {
}
}
- return this.getExistingRitual(web3Provider, ritualId);
+ return ritualId;
}
private static waitUntilRitualEnd = async (
diff --git a/src/policies/policy.ts b/src/policies/policy.ts
index 1ccca6a90..b17d6b81f 100644
--- a/src/policies/policy.ts
+++ b/src/policies/policy.ts
@@ -118,6 +118,11 @@ export class BlockchainPolicy {
public async generatePreEnactedPolicy(
ursulas: readonly Ursula[]
): Promise {
+ if (ursulas.length != this.verifiedKFrags.length) {
+ throw new Error(
+ `Number of ursulas must match number of verified kFrags: ${this.verifiedKFrags.length}`
+ );
+ }
const treasureMap = this.makeTreasureMap(ursulas, this.verifiedKFrags);
const encryptedTreasureMap = this.encryptTreasureMap(treasureMap);
// const revocationKit = new RevocationKit(treasureMap, this.publisher.signer);
diff --git a/src/sdk/cohort.ts b/src/sdk/cohort.ts
index 8571f3816..070027186 100644
--- a/src/sdk/cohort.ts
+++ b/src/sdk/cohort.ts
@@ -26,6 +26,15 @@ export class Cohort {
include: string[] = [],
exclude: string[] = []
) {
+ if (configuration.threshold > configuration.shares) {
+ throw new Error('Threshold cannot be greater than the number of shares');
+ }
+ // TODO: Remove this limitation after `nucypher-core@0.11.0` deployment
+ const isMultipleOf2 = (n: number) => n % 2 === 0;
+ if (!isMultipleOf2(configuration.shares)) {
+ throw new Error('Number of shares must be a multiple of 2');
+ }
+
const porter = new Porter(configuration.porterUri);
const ursulas = await porter.getUrsulas(
configuration.shares,
diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts
index 2b6887371..dbc2a6766 100644
--- a/src/sdk/strategy/cbd-strategy.ts
+++ b/src/sdk/strategy/cbd-strategy.ts
@@ -30,12 +30,21 @@ export class CbdStrategy {
}
public async deploy(
- web3Provider: ethers.providers.Web3Provider
+ web3Provider: ethers.providers.Web3Provider,
+ ritualId?: number
): Promise {
- const dkgRitual = await DkgClient.initializeRitual(
- web3Provider,
- this.cohort.ursulaAddresses
- );
+ if (ritualId === undefined) {
+ ritualId = await DkgClient.initializeRitual(
+ web3Provider,
+ 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.getExistingRitual(web3Provider, ritualId);
return DeployedCbdStrategy.create(this.cohort, dkgRitual);
}
diff --git a/test/acceptance/alice-grants.test.ts b/test/acceptance/alice-grants.test.ts
index a5fce2365..5460d43b3 100644
--- a/test/acceptance/alice-grants.test.ts
+++ b/test/acceptance/alice-grants.test.ts
@@ -31,7 +31,7 @@ describe('story: alice shares message with bob through policy', () => {
const shares = 3;
const startDate = new Date();
const endDate = new Date(Date.now() + 60 * 1000);
- const mockedUrsulas = fakeUrsulas().slice(0, shares);
+ const mockedUrsulas = fakeUrsulas(shares);
// Intermediate variables used for mocking
let encryptedTreasureMap: EncryptedTreasureMap;
diff --git a/test/acceptance/delay-enact.test.ts b/test/acceptance/delay-enact.test.ts
index 61574df95..857e0e9f0 100644
--- a/test/acceptance/delay-enact.test.ts
+++ b/test/acceptance/delay-enact.test.ts
@@ -14,7 +14,7 @@ describe('story: alice1 creates a policy but alice2 enacts it', () => {
const shares = 3;
const startDate = new Date();
const endDate = new Date(Date.now() + 60 * 1000); // 60s later
- const mockedUrsulas = fakeUrsulas().slice(0, shares);
+ const mockedUrsulas = fakeUrsulas(shares);
const label = 'fake-data-label';
it('alice generates a new policy', async () => {
diff --git a/test/docs/cbd.test.ts b/test/docs/cbd.test.ts
index cac579632..7538ba827 100644
--- a/test/docs/cbd.test.ts
+++ b/test/docs/cbd.test.ts
@@ -61,8 +61,8 @@ describe('Get Started (CBD PoC)', () => {
// 2. Build a Cohort
const config = {
- threshold: 3,
- shares: 5,
+ threshold: 2,
+ shares: 4,
porterUri: 'https://porter-tapir.nucypher.community',
};
const newCohort = await Cohort.create(config);
diff --git a/test/integration/pre.test.ts b/test/integration/pre.test.ts
index e3087e16c..6546228f5 100644
--- a/test/integration/pre.test.ts
+++ b/test/integration/pre.test.ts
@@ -15,7 +15,7 @@ describe('proxy reencryption', () => {
const plaintext = toBytes('plaintext-message');
const threshold = 2;
const shares = 3;
- const ursulas = fakeUrsulas().slice(0, shares);
+ const ursulas = fakeUrsulas(shares);
const label = 'fake-data-label';
const alice = fakeAlice();
const bob = fakeBob();
diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts
index 9723b328d..7f6f5b720 100644
--- a/test/unit/cbd-strategy.test.ts
+++ b/test/unit/cbd-strategy.test.ts
@@ -14,6 +14,7 @@ import {
fakeWeb3Provider,
makeCohort,
mockCbdDecrypt,
+ mockGetExistingRitual,
mockGetParticipants,
mockGetRitualState,
mockGetUrsulas,
@@ -38,8 +39,9 @@ const ownsNFT = new ERC721Ownership({
chain: 5,
});
const conditionExpr = new ConditionExpression(ownsNFT);
-const ursulas = fakeUrsulas().slice(0, 3);
+const ursulas = fakeUrsulas();
const variant = FerveoVariant.Precomputed;
+const ritualId = 0;
const makeCbdStrategy = async () => {
const cohort = await makeCohort(ursulas);
@@ -55,11 +57,13 @@ async function makeDeployedCbdStrategy() {
const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const web3Provider = fakeWeb3Provider(aliceSecretKey.toBEBytes());
const getUrsulasSpy = mockGetUrsulas(ursulas);
- const initializeRitualSpy = mockInitializeRitual(mockedDkgRitual);
+ const initializeRitualSpy = mockInitializeRitual(ritualId);
+ const getExistingRitualSpy = mockGetExistingRitual(mockedDkgRitual);
const deployedStrategy = await strategy.deploy(web3Provider);
expect(getUrsulasSpy).toHaveBeenCalled();
expect(initializeRitualSpy).toHaveBeenCalled();
+ expect(getExistingRitualSpy).toHaveBeenCalled();
return { mockedDkg, deployedStrategy };
}
diff --git a/test/unit/cohort.test.ts b/test/unit/cohort.test.ts
index 40ab18ee8..e041bcad8 100644
--- a/test/unit/cohort.test.ts
+++ b/test/unit/cohort.test.ts
@@ -2,7 +2,7 @@ import { Cohort } from '../../src';
import { fakeUrsulas, makeCohort } from '../utils';
describe('Cohort', () => {
- const mockedUrsulas = fakeUrsulas().slice(0, 3);
+ const mockedUrsulas = fakeUrsulas();
it('creates a Cohort', async () => {
const cohort = await makeCohort(mockedUrsulas);
diff --git a/test/unit/pre-strategy.test.ts b/test/unit/pre-strategy.test.ts
index 9fa599a22..6a5e6d2f3 100644
--- a/test/unit/pre-strategy.test.ts
+++ b/test/unit/pre-strategy.test.ts
@@ -38,7 +38,7 @@ const ownsNFT = new ERC721Ownership({
chain: 5,
});
const conditionExpr = new ConditionExpression(ownsNFT);
-const mockedUrsulas = fakeUrsulas().slice(0, 3);
+const mockedUrsulas = fakeUrsulas();
const makePreStrategy = async () => {
const cohort = await makeCohort(mockedUrsulas);
diff --git a/test/utils.ts b/test/utils.ts
index 4ffaa1ba2..694b2f6fc 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -114,11 +114,12 @@ const genChecksumAddress = (i: number) =>
'0x' + '0'.repeat(40 - i.toString(16).length) + i.toString(16);
const genEthAddr = (i: number) =>
EthereumAddress.fromString(genChecksumAddress(i));
-export const fakeUrsulas = (): readonly Ursula[] =>
- [0, 1, 2, 3, 4].map((i: number) => ({
+export const fakeUrsulas = (n = 4): Ursula[] =>
+ // 0...n-1
+ Array.from(Array(n).keys()).map((i: number) => ({
encryptingKey: SecretKey.random().publicKey(),
checksumAddress: genChecksumAddress(i).toLowerCase(),
- uri: 'https://example.a.com:9151',
+ uri: `https://example.${i}.com:9151`,
}));
export const mockGetUrsulas = (ursulas: readonly Ursula[]) => {
@@ -516,8 +517,14 @@ export const fakeDkgRitual = (ritual: {
);
};
-export const mockInitializeRitual = (dkgRitual: DkgRitual) => {
+export const mockInitializeRitual = (ritualId: number) => {
return jest.spyOn(DkgClient, 'initializeRitual').mockImplementation(() => {
+ return Promise.resolve(ritualId);
+ });
+};
+
+export const mockGetExistingRitual = (dkgRitual: DkgRitual) => {
+ return jest.spyOn(DkgClient, 'getExistingRitual').mockImplementation(() => {
return Promise.resolve(dkgRitual);
});
};
@@ -526,7 +533,7 @@ export const makeCohort = async (ursulas: Ursula[]) => {
const getUrsulasSpy = mockGetUrsulas(ursulas);
const config = {
threshold: 2,
- shares: 3,
+ shares: ursulas.length,
porterUri: 'https://_this.should.crash',
};
const cohort = await Cohort.create(config);