Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
draft taco api
Browse files Browse the repository at this point in the history
piotr-roslaniec committed Aug 14, 2023
1 parent 36fd43e commit 2d6d92e
Showing 9 changed files with 203 additions and 27 deletions.
17 changes: 8 additions & 9 deletions src/characters/cbd-recipient.ts
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@ import { ethers } from 'ethers';

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

@@ -34,22 +33,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.Web3Provider,
web3Provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
ciphertext: Ciphertext
): Promise<Uint8Array> {
const decryptionShares = await this.retrieve(
provider,
web3Provider,
conditionExpr,
ciphertext
);
@@ -64,15 +63,15 @@ export class ThresholdDecrypter {

// Retrieve decryption shares
public async retrieve(
provider: ethers.providers.Web3Provider,
web3Provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
ciphertext: Ciphertext
): 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,
ciphertext,
5 changes: 5 additions & 0 deletions src/conditions/condition-expr.ts
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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,
13 changes: 13 additions & 0 deletions src/dkg.ts
Original file line number Diff line number Diff line change
@@ -133,6 +133,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> {
6 changes: 5 additions & 1 deletion src/sdk/strategy/cbd-strategy.ts
Original file line number Diff line number Diff line change
@@ -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);
}

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,
};
2 changes: 1 addition & 1 deletion test/unit/cbd-strategy.test.ts
Original file line number Diff line number Diff line change
@@ -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();
33 changes: 21 additions & 12 deletions test/unit/conditions/condition-expr.test.ts
Original file line number Diff line number Diff line change
@@ -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'
@@ -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'
@@ -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(
@@ -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
);
@@ -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();
@@ -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(() => {
@@ -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();
@@ -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();
@@ -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();
@@ -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();
@@ -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();
88 changes: 88 additions & 0 deletions test/unit/taco.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
FerveoVariant,
SecretKey,
SessionStaticSecret,
} from '@nucypher/nucypher-core';

import { conditions } from '../../src';
import { taco } from '../../src/taco';
import { toBytes } from '../../src/utils';
import {
fakeDkgFlow,
fakeDkgParticipants,
fakeDkgRitual,
fakePorterUri,
fakeTDecFlow,
fakeWeb3Provider,
mockCbdDecrypt,
mockGetExistingRitual,
mockGetParticipants,
mockRandomSessionStaticSecret,
} from '../utils';

import { aliceSecretKeyBytes } from './testVariables';

const {
predefined: { ERC721Ownership },
ConditionExpression,
} = conditions;

// Shared test variables
const aliceSecretKey = SecretKey.fromBEBytes(aliceSecretKeyBytes);
const ownsNFT = new ERC721Ownership({
contractAddress: '0x1e988ba4692e52Bc50b375bcC8585b95c48AaD77',
parameters: [3591],
chain: 5,
});
const conditionExpr = new ConditionExpression(ownsNFT);
const variant = FerveoVariant.precomputed;
// const ritualId = 0;
const message = 'this is a secret';

describe('taco', () => {
it('encrypts and decrypts', async () => {
const mockedDkg = fakeDkgFlow(variant, 0, 4, 4);
const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const web3Provider = fakeWeb3Provider(aliceSecretKey.toBEBytes());
const getExistingRitualSpy = mockGetExistingRitual(mockedDkgRitual);

const tacoMk = await taco.encrypt(
web3Provider,
message,
conditionExpr,
mockedDkg.ritualId
);

expect(getExistingRitualSpy).toHaveBeenCalled();

const { decryptionShares } = fakeTDecFlow({
...mockedDkg,
message: toBytes(message),
aad: tacoMk.aad,
ciphertext: tacoMk.ciphertext,
});
const { participantSecrets, participants } = fakeDkgParticipants(
mockedDkg.ritualId,
variant
);
const requesterSessionKey = SessionStaticSecret.random();
const decryptSpy = mockCbdDecrypt(
mockedDkg.ritualId,
decryptionShares,
participantSecrets,
requesterSessionKey.publicKey()
);
const getParticipantsSpy = mockGetParticipants(participants);
const sessionKeySpy = mockRandomSessionStaticSecret(requesterSessionKey);

const decryptedMessage = await taco.decrypt(
web3Provider,
tacoMk,
fakePorterUri
);
expect(getParticipantsSpy).toHaveBeenCalled();
expect(sessionKeySpy).toHaveBeenCalled();
expect(decryptSpy).toHaveBeenCalled();
expect(decryptedMessage).toEqual(toBytes(message));
});
});
5 changes: 1 addition & 4 deletions test/utils.ts
Original file line number Diff line number Diff line change
@@ -304,10 +304,7 @@ export const fakeTDecFlow = ({
message,
}: FakeDkgRitualFlow) => {
// Having aggregated the transcripts, the validators can now create decryption shares
const decryptionShares: (
| DecryptionSharePrecomputed
| DecryptionShareSimple
)[] = [];
const decryptionShares: DecryptionShareSimple[] = [];
zip(validators, validatorKeypairs).forEach(([validator, keypair]) => {
const dkg = new Dkg(ritualId, sharesNum, threshold, validators, validator);
const aggregate = dkg.aggregateTranscript(receivedMessages);

1 comment on commit 2d6d92e

@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: 433.59 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: 687.50 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.36 MB

Please sign in to comment.