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);