Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeout #251

Closed
wants to merge 12 commits into from
Next Next commit
feat: implement local ritual verificaiton
piotr-roslaniec committed Jul 10, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 39241c0cacc2b29bf11a5f4c88a6d53bb8ea4375
32 changes: 27 additions & 5 deletions src/agents/coordinator.ts
Original file line number Diff line number Diff line change
@@ -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<DkgParticipant[]> {
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<CoordinatorRitual> {
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<DkgRitualState> {
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<Coordinator> {
const network = await provider.getNetwork();
8 changes: 5 additions & 3 deletions src/agents/subscription-manager.ts
Original file line number Diff line number Diff line change
@@ -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<SubscriptionManager> {
const network = await provider.getNetwork();
33 changes: 29 additions & 4 deletions src/characters/cbd-recipient.ts
Original file line number Diff line number Diff line change
@@ -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<DecryptionSharePrecomputed[] | DecryptionShareSimple[]> {
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,
85 changes: 58 additions & 27 deletions src/dkg.ts
Original file line number Diff line number Diff line change
@@ -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<string, FerveoPublicKey> = {
'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<DkgRitual> {
public static async initializeRitual(
web3Provider: ethers.providers.Web3Provider,
ritualParams: {
shares: number;
threshold: number;
}
): Promise<DkgRitual> {
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<boolean> {
// 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<boolean> {
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);
}
}
8 changes: 5 additions & 3 deletions src/sdk/strategy/cbd-strategy.ts
Original file line number Diff line number Diff line change
@@ -30,14 +30,16 @@ export class CbdStrategy {
}

public async deploy(
provider: ethers.providers.Web3Provider
web3Provider: ethers.providers.Web3Provider
): Promise<DeployedCbdStrategy> {
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);
}

6 changes: 6 additions & 0 deletions test/unit/cbd-strategy.test.ts
Original file line number Diff line number Diff line change
@@ -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();
33 changes: 27 additions & 6 deletions test/utils.ts
Original file line number Diff line number Diff line change
@@ -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<DkgRitual>;
})
);
};

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