Skip to content

Commit

Permalink
Implement the TACo API module (#263)
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec authored Sep 12, 2023
2 parents c18f996 + e1a0cd1 commit ca9e163
Show file tree
Hide file tree
Showing 12 changed files with 1,131 additions and 127 deletions.
905 changes: 854 additions & 51 deletions abi/Coordinator.json

Large diffs are not rendered by default.

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

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

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

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

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

private static async connectReadOnly(provider: ethers.providers.Provider) {
return await this.connect(provider);
}
Expand Down
11 changes: 5 additions & 6 deletions src/characters/cbd-recipient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { ethers } from 'ethers';

import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator';
import { ConditionContext } from '../conditions';
import { DkgRitual } from '../dkg';
import { PorterClient } from '../porter';
import { fromJSON, objectEquals, toJSON } from '../utils';

Expand All @@ -31,22 +30,22 @@ export class ThresholdDecrypter {
private readonly threshold: number
) {}

public static create(porterUri: string, dkgRitual: DkgRitual) {
public static create(porterUri: string, ritualId: number, threshold: number) {
return new ThresholdDecrypter(
new PorterClient(porterUri),
dkgRitual.id,
dkgRitual.dkgParams.threshold
ritualId,
threshold
);
}

// Retrieve and decrypt ciphertext using provider and condition expression
public async retrieveAndDecrypt(
provider: ethers.providers.Provider,
web3Provider: ethers.providers.Provider,
thresholdMessageKit: ThresholdMessageKit,
signer?: ethers.Signer
): Promise<Uint8Array> {
const decryptionShares = await this.retrieve(
provider,
web3Provider,
thresholdMessageKit,
signer
);
Expand Down
2 changes: 1 addition & 1 deletion src/conditions/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class ConditionContext {
);
if (conditionRequiresSigner && !this.signer) {
throw new Error(
`Condition contains ${USER_ADDRESS_PARAM} context variable and requires a signer to populate`
`Signer required to satisfy ${USER_ADDRESS_PARAM} context variable in condition`
);
}

Expand Down
61 changes: 37 additions & 24 deletions src/dkg.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
import { DkgPublicKey } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import { BigNumberish, ethers } from 'ethers';

import { DkgCoordinatorAgent, DkgRitualState } from './agents/coordinator';
import { ChecksumAddress } from './types';
import { fromHexString, objectEquals } from './utils';

export type DkgRitualParameters = {
sharesNum: number;
threshold: number;
};
import { fromHexString } from './utils';

export interface DkgRitualJSON {
id: number;
dkgPublicKey: Uint8Array;
dkgParams: DkgRitualParameters;
sharesNum: number;
threshold: number;
state: DkgRitualState;
}

export class DkgRitual {
constructor(
public readonly id: number,
public readonly dkgPublicKey: DkgPublicKey,
public readonly dkgParams: DkgRitualParameters,
public readonly sharesNum: number,
public readonly threshold: number,
public readonly state: DkgRitualState
) {}

public toObj(): DkgRitualJSON {
return {
id: this.id,
dkgPublicKey: this.dkgPublicKey.toBytes(),
dkgParams: this.dkgParams,
sharesNum: this.sharesNum,
threshold: this.threshold,
state: this.state,
};
}

public static fromObj({
id,
dkgPublicKey,
dkgParams,
sharesNum,
threshold,
state,
}: DkgRitualJSON): DkgRitual {
return new DkgRitual(
id,
DkgPublicKey.fromBytes(dkgPublicKey),
dkgParams,
sharesNum,
threshold,
state
);
}
Expand All @@ -52,28 +52,30 @@ export class DkgRitual {
return [
this.id === other.id,
this.dkgPublicKey.equals(other.dkgPublicKey),
objectEquals(this.dkgParams, other.dkgParams),
this.sharesNum === other.sharesNum,
this.threshold === other.threshold,
this.state === other.state,
].every(Boolean);
}
}

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

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

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

public static async getExistingRitual(
public static async getRitual(
provider: ethers.providers.Provider,
ritualId: number
): Promise<DkgRitual> {
Expand All @@ -127,14 +129,25 @@ export class DkgClient {
return new DkgRitual(
ritualId,
DkgPublicKey.fromBytes(dkgPkBytes),
{
sharesNum: ritual.dkgSize,
threshold: assumedThreshold(ritual.dkgSize),
},
ritual.dkgSize,
ritual.threshold,
ritualState
);
}

public static async getFinalizedRitual(
provider: ethers.providers.Provider,
ritualId: number
): Promise<DkgRitual> {
const ritual = await DkgClient.getRitual(provider, ritualId);
if (ritual.state !== DkgRitualState.FINALIZED) {
throw new Error(
`Ritual ${ritualId} is not finalized. State: ${ritual.state}`
);
}
return ritual;
}

// 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> {
Expand Down
10 changes: 7 additions & 3 deletions src/sdk/strategy/cbd-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class CbdStrategy {
// // Given that we just initialized the ritual, this should never happen
// throw new Error('Ritual ID is undefined');
// }
const dkgRitual = await DkgClient.getExistingRitual(provider, ritualId);
const dkgRitual = await DkgClient.getRitual(provider, ritualId);
return DeployedCbdStrategy.create(dkgRitual, this.cohort.porterUri);
}

Expand Down Expand Up @@ -78,7 +78,11 @@ export class DeployedCbdStrategy {
) {}

public static create(dkgRitual: DkgRitual, porterUri: string) {
const decrypter = ThresholdDecrypter.create(porterUri, dkgRitual);
const decrypter = ThresholdDecrypter.create(
porterUri,
dkgRitual.id,
dkgRitual.threshold
);
return new DeployedCbdStrategy(decrypter, dkgRitual.dkgPublicKey);
}

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

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

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

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

export const encryptLight = async (
message: string,
condition: Condition,
dkgPublicKey: DkgPublicKey
): Promise<ThresholdMessageKit> => {
const encrypter = new Enrico(dkgPublicKey);
const conditionExpr = new ConditionExpression(condition);
return encrypter.encryptMessageCbd(toBytes(message), conditionExpr);
};

export const decrypt = async (
provider: ethers.providers.Provider,
messageKit: ThresholdMessageKit,
signer?: ethers.Signer,
porterUri = getPorterUri('tapir')
): Promise<Uint8Array> => {
const ritualId = await DkgCoordinatorAgent.getRitualIdFromPublicKey(
provider,
messageKit.acp.publicKey
);
const ritual = await DkgClient.getFinalizedRitual(provider, ritualId);
const decrypter = ThresholdDecrypter.create(
porterUri,
ritualId,
ritual.threshold
);
return decrypter.retrieveAndDecrypt(provider, messageKit, signer);
};

export const taco = {
encrypt,
encryptLight,
decrypt,
};
Loading

0 comments on commit ca9e163

Please sign in to comment.