diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts index ca1e41f17..a30deb191 100644 --- a/src/agents/coordinator.ts +++ b/src/agents/coordinator.ts @@ -25,6 +25,7 @@ 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/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 68ad97b8a..17af50e8b 100644 --- a/src/characters/cbd-recipient.ts +++ b/src/characters/cbd-recipient.ts @@ -20,6 +20,7 @@ import { } from '../agents/coordinator'; import { ConditionExpression } from '../conditions'; import { + DkgClient, DkgRitual, getCombineDecryptionSharesFunction, getVariantClass, @@ -91,6 +92,16 @@ export class ThresholdDecrypter { ); } + 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( web3Provider, this.ritualId diff --git a/src/dkg.ts b/src/dkg.ts index a728b7a59..749dae96f 100644 --- a/src/dkg.ts +++ b/src/dkg.ts @@ -1,11 +1,16 @@ import { + AggregatedTranscript, combineDecryptionSharesPrecomputed, combineDecryptionSharesSimple, DecryptionSharePrecomputed, DecryptionShareSimple, DkgPublicKey, + EthereumAddress, + FerveoPublicKey, FerveoVariant, SharedSecret, + Validator, + ValidatorMessage, } from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; @@ -97,6 +102,21 @@ export class DkgRitual { const assumedThreshold = (sharesNum: number): number => Math.floor(sharesNum / 2) + 1; +// 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( + 'ace9d7567b26dafc512b2303cfdaa872850c62b100078ddeaabf8408c7308b3a43dfeb88375c21ef63230fb4008ce7e908764463c6765e556f9b03009eb1757d179eaa26bf875332807cc070d62a385ed2e66e09f4f4766451da12779a09036e' + ) + ), + '0xb15d5A4e2be34f4bE154A1b08a94Ab920FfD8A41': FerveoPublicKey.fromBytes( + fromHexString( + '8b373fdb6b43e9dca028bd603c2bf90f0e008ec83ff217a8d7bc006b585570e6ab1ce761bad0e21c1aed1363286145f61134ed0ab53f4ebaa05036396c57f6e587f33d49667c1003cd03b71ad651b09dd4791bc631eaef93f1b313bbee7bd63a' + ) + ), +}; + export class DkgClient { public static async initializeRitual( web3Provider: ethers.providers.Web3Provider, @@ -167,24 +187,34 @@ export class DkgClient { ); } - // 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/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts index b580673c3..fe19d0350 100644 --- a/test/unit/cbd-strategy.test.ts +++ b/test/unit/cbd-strategy.test.ts @@ -20,6 +20,7 @@ import { mockGetUrsulas, mockInitializeRitual, mockRandomSessionStaticSecret, + mockVerifyRitual, } from '../utils'; import { aliceSecretKeyBytes } from './testVariables'; @@ -133,6 +134,7 @@ describe('CbdDeployedStrategy', () => { const getUrsulasSpy = mockGetUrsulas(ursulas); const sessionKeySpy = mockRandomSessionStaticSecret(requesterSessionKey); const getRitualStateSpy = mockGetRitualState(); + const verifyRitualSpy = mockVerifyRitual(); const decryptedMessage = await deployedStrategy.decrypter.retrieveAndDecrypt( @@ -141,6 +143,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 b9b76db06..f33791cec 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -546,3 +546,9 @@ export const mockGetRitualState = (state = DkgRitualState.FINALIZED) => { .spyOn(DkgCoordinatorAgent, 'getRitualState') .mockImplementation((_provider, _ritualId) => Promise.resolve(state)); }; + +export const mockVerifyRitual = (isValid = true) => { + return jest + .spyOn(DkgClient, 'verifyRitual') + .mockImplementation((_provider, _ritualId) => Promise.resolve(isValid)); +};