Skip to content

Commit

Permalink
draft taco api
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Aug 14, 2023
1 parent e4f824d commit 60af38b
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 84 deletions.
47 changes: 14 additions & 33 deletions src/characters/cbd-recipient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
Ciphertext,
combineDecryptionSharesSimple,
Context,
DecryptionSharePrecomputed,
DecryptionShareSimple,
decryptWithSharedSecret,
EncryptedThresholdDecryptionRequest,
Expand All @@ -15,11 +15,6 @@ import { ethers } from 'ethers';

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

Expand All @@ -38,31 +33,27 @@ 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.Web3Provider,
web3Provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
variant: FerveoVariant,
ciphertext: Ciphertext
): Promise<Uint8Array> {
const decryptionShares = await this.retrieve(
provider,
web3Provider,
conditionExpr,
variant,
ciphertext
);

const combineDecryptionSharesFn =
getCombineDecryptionSharesFunction(variant);
const sharedSecret = combineDecryptionSharesFn(decryptionShares);
const sharedSecret = combineDecryptionSharesSimple(decryptionShares);
return decryptWithSharedSecret(
ciphertext,
conditionExpr.asAad(),
Expand All @@ -72,19 +63,17 @@ export class ThresholdDecrypter {

// Retrieve decryption shares
public async retrieve(
provider: ethers.providers.Web3Provider,
web3Provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
variant: FerveoVariant,
ciphertext: Ciphertext
): Promise<DecryptionSharePrecomputed[] | DecryptionShareSimple[]> {
): Promise<DecryptionShareSimple[]> {
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,
ciphertext,
conditionExpr,
contextStr,
Expand All @@ -106,15 +95,13 @@ export class ThresholdDecrypter {
return this.makeDecryptionShares(
encryptedResponses,
sharedSecrets,
variant,
this.ritualId
);
}

private makeDecryptionShares(
encryptedResponses: Record<string, EncryptedThresholdDecryptionResponse>,
sessionSharedSecret: Record<string, SessionSharedSecret>,
variant: FerveoVariant,
expectedRitualId: number
) {
const decryptedResponses = Object.entries(encryptedResponses).map(
Expand All @@ -128,19 +115,13 @@ export class ThresholdDecrypter {
);
}

const decryptionShares = decryptedResponses.map(
({ decryptionShare }) => decryptionShare
);

const DecryptionShareType = getVariantClass(variant);
return decryptionShares.map((share) =>
DecryptionShareType.fromBytes(share)
return decryptedResponses.map(({ decryptionShare }) =>
DecryptionShareSimple.fromBytes(decryptionShare)
);
}

private makeDecryptionRequests(
ritualId: number,
variant: FerveoVariant,
ciphertext: Ciphertext,
conditionExpr: ConditionExpression,
contextStr: string,
Expand All @@ -151,7 +132,7 @@ export class ThresholdDecrypter {
} {
const decryptionRequest = new ThresholdDecryptionRequest(
ritualId,
variant,
FerveoVariant.simple,
ciphertext,
conditionExpr.toWASMConditions(),
new Context(contextStr)
Expand Down
5 changes: 5 additions & 0 deletions src/conditions/condition-expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Conditions as WASMConditions } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import { SemVer } from 'semver';

import { fromBytes } from '../../test/utils';
import { objectEquals, toBytes, toJSON } from '../utils';

import {
Expand Down Expand Up @@ -94,6 +95,10 @@ export class ConditionExpression {
return toBytes(this.toJson());
}

public static fromAad(aad: Uint8Array): ConditionExpression {
return ConditionExpression.fromJSON(fromBytes(aad));
}

public equals(other: ConditionExpression): boolean {
return [
this.version === other.version,
Expand Down
13 changes: 13 additions & 0 deletions src/dkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,19 @@ export class DkgClient {
);
}

public static async getFinalizedRitual(
web3Provider: ethers.providers.Web3Provider,
ritualId: number
): Promise<DkgRitual> {
const ritual = await DkgClient.getExistingRitual(web3Provider, 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
6 changes: 5 additions & 1 deletion src/sdk/strategy/cbd-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,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.dkgParams.threshold
);
return new DeployedCbdStrategy(decrypter, dkgRitual.dkgPublicKey);
}

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

import { ThresholdDecrypter } from './characters/cbd-recipient';
import { ConditionExpression } from './conditions';
import { DkgClient } from './dkg';
import { toBytes } from './utils';

export interface TacoMessageKit {
ciphertext: Ciphertext;
aad: Uint8Array;
ritualId: number;
threshold: number;
}

export const encrypt = async (
web3Provider: ethers.providers.Web3Provider,
message: string,
conditions: ConditionExpression,
ritualId: number
): Promise<TacoMessageKit> => {
const dkgRitual = await DkgClient.getFinalizedRitual(web3Provider, ritualId);
const aad = conditions.asAad();
const ciphertext = ferveoEncrypt(
toBytes(message),
aad,
dkgRitual.dkgPublicKey
);
return {
ciphertext,
aad,
ritualId,
threshold: dkgRitual.dkgParams.threshold,
};
};

export const decrypt = async (
web3Provider: ethers.providers.Web3Provider,
messageKit: TacoMessageKit,
porterUri: string
): Promise<Uint8Array> => {
const decrypter = ThresholdDecrypter.create(
porterUri,
messageKit.ritualId,
messageKit.threshold
);
const condExpr = ConditionExpression.fromAad(messageKit.aad);
// TODO: Need web3Provider to fetch participants from Coordinator to make decryption requests.
// Should we put them into the message kit instead?
// Consider case where participants are changing over time. Is that an issue we should consider now?
return decrypter.retrieveAndDecrypt(
web3Provider,
condExpr,
messageKit.ciphertext
);
};

export const taco = {
encrypt,
decrypt,
};
4 changes: 1 addition & 3 deletions test/unit/cbd-strategy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ const makeCbdStrategy = async () => {

async function makeDeployedCbdStrategy() {
const strategy = await makeCbdStrategy();

const mockedDkg = fakeDkgFlow(variant, 0, 4, 4);
const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const web3Provider = fakeWeb3Provider(aliceSecretKey.toBEBytes());
const getUrsulasSpy = mockGetUrsulas(ursulas);
const initializeRitualSpy = mockInitializeRitual(ritualId);
const getExistingRitualSpy = mockGetExistingRitual(mockedDkgRitual);

const deployedStrategy = await strategy.deploy(web3Provider);

expect(getUrsulasSpy).toHaveBeenCalled();
Expand Down Expand Up @@ -112,7 +112,6 @@ describe('CbdDeployedStrategy', () => {
// Setup mocks for `retrieveAndDecrypt`
const { decryptionShares } = fakeTDecFlow({
...mockedDkg,
variant,
message: toBytes(message),
aad,
ciphertext,
Expand All @@ -136,7 +135,6 @@ describe('CbdDeployedStrategy', () => {
await deployedStrategy.decrypter.retrieveAndDecrypt(
aliceProvider,
conditionExpr,
variant,
ciphertext
);
expect(getUrsulasSpy).toHaveBeenCalled();
Expand Down
33 changes: 21 additions & 12 deletions test/unit/conditions/condition-expr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('condition set', () => {
).toBeTruthy();
});

it('different minor/patch version but same condition', async () => {
it('different minor/patch version but same condition', () => {
const conditionExprOlderMinorVersion = new ConditionExpression(
rpcCondition,
'0.1.0'
Expand All @@ -97,7 +97,7 @@ describe('condition set', () => {
).not.toBeTruthy();
});

it('minor/patch number greater than major; still older', async () => {
it('minor/patch number greater than major; still older', () => {
const conditionExprOlderMinorVersion = new ConditionExpression(
rpcCondition,
'0.9.0'
Expand Down Expand Up @@ -140,7 +140,7 @@ describe('condition set', () => {
contractConditionWithAbi,
timeCondition,
compoundCondition,
])('same version but different condition', async (condition) => {
])('same version but different condition', (condition) => {
const conditionExprSameVersionDifferentCondition =
new ConditionExpression(condition);
expect(
Expand All @@ -150,7 +150,7 @@ describe('condition set', () => {
).not.toBeTruthy();
});

it('same contract condition although using erc721 helper', async () => {
it('same contract condition although using erc721 helper', () => {
const erc721ConditionExpr = new ConditionExpression(
erc721BalanceCondition
);
Expand All @@ -171,7 +171,7 @@ describe('condition set', () => {
rpcCondition,
timeCondition,
compoundCondition,
])('serializes to and from json', async (condition) => {
])('serializes to and from json', (condition) => {
const conditionExpr = new ConditionExpression(condition);
const conditionExprJson = conditionExpr.toJson();
expect(conditionExprJson).toBeDefined();
Expand All @@ -186,7 +186,16 @@ describe('condition set', () => {
expect(conditionExprFromJson.equals(conditionExprFromJson)).toBeTruthy();
});

it('incompatible version', async () => {
it('serializes to and from aad', () => {
const conditionExpr = new ConditionExpression(rpcCondition);
const conditionExprAad = conditionExpr.asAad();
const conditionExprFromAad =
ConditionExpression.fromAad(conditionExprAad);
expect(conditionExprFromAad).toBeDefined();
expect(conditionExprFromAad.equals(conditionExpr)).toBeTruthy();
});

it('incompatible version', () => {
const currentVersion = new SemVer(ConditionExpression.VERSION);
const invalidVersion = currentVersion.inc('major');
expect(() => {
Expand Down Expand Up @@ -249,13 +258,13 @@ describe('condition set', () => {
method: 'isPolicyActive',
},
},
])("can't determine condition type", async (invalidCondition) => {
])("can't determine condition type", (invalidCondition) => {
expect(() => {
ConditionExpression.fromObj(invalidCondition);
}).toThrow('unrecognized condition data');
});

it('erc721 condition serialization', async () => {
it('erc721 condition serialization', () => {
const conditionExpr = new ConditionExpression(erc721BalanceCondition);

const erc721BalanceConditionObj = erc721BalanceCondition.toObj();
Expand Down Expand Up @@ -283,7 +292,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition);
});

it('contract condition no abi serialization', async () => {
it('contract condition no abi serialization', () => {
const conditionExpr = new ConditionExpression(contractConditionNoAbi);

const conditionExprJson = conditionExpr.toJson();
Expand Down Expand Up @@ -315,7 +324,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition);
});

it('contract condition with abi serialization', async () => {
it('contract condition with abi serialization', () => {
const conditionExpr = new ConditionExpression(contractConditionWithAbi);

const conditionExprJson = conditionExpr.toJson();
Expand Down Expand Up @@ -348,7 +357,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition);
});

it('time condition serialization', async () => {
it('time condition serialization', () => {
const conditionExpr = new ConditionExpression(timeCondition);

const conditionExprJson = conditionExpr.toJson();
Expand All @@ -371,7 +380,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(TimeCondition);
});

it('rpc condition serialization', async () => {
it('rpc condition serialization', () => {
const conditionExpr = new ConditionExpression(rpcCondition);

const conditionExprJson = conditionExpr.toJson();
Expand Down
Loading

1 comment on commit 60af38b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundled size for the package is listed below:

build/module/src/kits: 19.53 KB
build/module/src/characters: 74.22 KB
build/module/src/policies: 19.53 KB
build/module/src/agents: 39.06 KB
build/module/src/conditions/predefined: 19.53 KB
build/module/src/conditions/base: 54.69 KB
build/module/src/conditions/context: 42.97 KB
build/module/src/conditions: 156.25 KB
build/module/src/sdk/strategy: 31.25 KB
build/module/src/sdk: 42.97 KB
build/module/src: 437.50 KB
build/module/types/ethers-contracts/factories: 82.03 KB
build/module/types/ethers-contracts: 152.34 KB
build/module/types: 156.25 KB
build/module/test: 42.97 KB
build/module: 691.41 KB
build/main/src/kits: 19.53 KB
build/main/src/characters: 74.22 KB
build/main/src/policies: 19.53 KB
build/main/src/agents: 39.06 KB
build/main/src/conditions/predefined: 19.53 KB
build/main/src/conditions/base: 54.69 KB
build/main/src/conditions/context: 42.97 KB
build/main/src/conditions: 156.25 KB
build/main/src/sdk/strategy: 35.16 KB
build/main/src/sdk: 46.88 KB
build/main/src: 445.31 KB
build/main/types/ethers-contracts/factories: 82.03 KB
build/main/types/ethers-contracts: 152.34 KB
build/main/types: 156.25 KB
build/main/test: 46.88 KB
build/main: 703.13 KB
build: 1.37 MB

Please sign in to comment.