Skip to content

Commit

Permalink
tdec runs e2e
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Jun 27, 2023
1 parent 87e237f commit a8aaf8e
Show file tree
Hide file tree
Showing 16 changed files with 702 additions and 686 deletions.
1,163 changes: 565 additions & 598 deletions abi/Coordinator.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"prebuild": "yarn typechain"
},
"dependencies": {
"@nucypher/nucypher-core": "../nucypher-core/nucypher-core-wasm/pkg",
"@nucypher/nucypher-core": "^0.10.0",
"axios": "^0.21.1",
"deep-equal": "^2.2.1",
"ethers": "^5.4.1",
Expand Down
45 changes: 41 additions & 4 deletions src/agents/coordinator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { SessionStaticKey } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';

import {
Coordinator,
Coordinator__factory,
} from '../../types/ethers-contracts';
import { BLS12381 } from '../../types/ethers-contracts/Coordinator';
import { fromHexString } from '../utils';

import { getContract } from './contracts';

Expand All @@ -19,25 +21,60 @@ export interface CoordinatorRitual {
aggregatedTranscript: string;
}

export type DkgParticipant = Coordinator.ParticipantStructOutput;
export type DkgParticipant = {
provider: string;
aggregated: boolean;
decryptionRequestStaticKey: SessionStaticKey;
};

export class DkgCoordinatorAgent {
public static async getParticipants(
provider: ethers.providers.Provider,
ritualId: number
): Promise<DkgParticipant[]> {
const Coordinator = await this.connectReadOnly(provider);
// TODO: Remove `as unknown` cast after regenerating the contract types: https://github.com/nucypher/nucypher-contracts/pull/77
const participants = await Coordinator.getParticipants(ritualId);
return participants as unknown as DkgParticipant[];

return participants.map((participant) => {
return {
provider: participant.provider,
aggregated: participant.aggregated,
decryptionRequestStaticKey: SessionStaticKey.fromBytes(
fromHexString(participant.decryptionRequestStaticKey)
),
};
});
// return [
// {
// provider: '0x210eeAC07542F815ebB6FD6689637D8cA2689392',
// aggregated: true,
// decryptionRequestStaticKey:
// '5453536b00020000c4204e21666ff316083f96fdb83111940df3ed28b7cedc836321ad0555dc81f04e75',
// },
// {
// provider: '0xb15d5A4e2be34f4bE154A1b08a94Ab920FfD8A41',
// aggregated: true,
// decryptionRequestStaticKey:
// '5453536b00020000c4206407ba0e8140ae106382db8dcdbd0689505717c25a1155c1bccdd49b18d6f252',
// },
// ].map((participant) => {
// return {
// provider: participant.provider,
// aggregated: participant.aggregated,
// // transcript: Transcript.fromBytes(fromHexString(participant.transcript)),
// decryptionRequestStaticKey: SessionStaticKey.fromBytes(
// fromHexString(participant.decryptionRequestStaticKey)
// ),
// };
// });
}

public static async getRitual(
provider: ethers.providers.Provider,
ritualId: number
): Promise<CoordinatorRitual> {
const Coordinator = await this.connectReadOnly(provider);
return await Coordinator.rituals(ritualId);
return Coordinator.rituals(ritualId);
}

private static async connectReadOnly(provider: ethers.providers.Provider) {
Expand Down
28 changes: 17 additions & 11 deletions src/characters/cbd-recipient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
EncryptedThresholdDecryptionRequest,
EncryptedThresholdDecryptionResponse,
SessionSharedSecret,
SessionStaticKey,
SessionStaticSecret,
ThresholdDecryptionRequest,
} from '@nucypher/nucypher-core';
Expand All @@ -17,10 +16,11 @@ import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator';
import { ConditionExpression } from '../conditions';
import {
DkgRitual,
FerveoVariant,
getCombineDecryptionSharesFunction,
getVariantClass,
} from '../dkg';
import { fromHexString, fromJSON, toJSON } from '../utils';
import { fromJSON, toJSON } from '../utils';

import { Porter } from './porter';

Expand All @@ -47,13 +47,12 @@ export class CbdTDecDecrypter {
);
}

// Retrieve and decrypt ciphertext using provider and condition set
// Retrieve and decrypt ciphertext using provider and condition expression
public async retrieveAndDecrypt(
provider: ethers.providers.Web3Provider,
conditionExpr: ConditionExpression,
variant: number,
ciphertext: Ciphertext,
aad: Uint8Array
variant: FerveoVariant,
ciphertext: Ciphertext
): Promise<Uint8Array> {
const decryptionShares = await this.retrieve(
provider,
Expand All @@ -65,7 +64,11 @@ export class CbdTDecDecrypter {
const combineDecryptionSharesFn =
getCombineDecryptionSharesFunction(variant);
const sharedSecret = combineDecryptionSharesFn(decryptionShares);
return decryptWithSharedSecret(ciphertext, aad, sharedSecret);
return decryptWithSharedSecret(
ciphertext,
conditionExpr.asAad(),
sharedSecret
);
}

// Retrieve decryption shares
Expand All @@ -79,21 +82,25 @@ export class CbdTDecDecrypter {
provider,
this.ritualId
);
// We only need the `threshold` participants
const sufficientDkgParticipants = dkgParticipants.slice(0, this.threshold);
const contextStr = await conditionExpr.buildContext(provider).toJson();
const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests(
this.ritualId,
variant,
ciphertext,
conditionExpr,
contextStr,
dkgParticipants
sufficientDkgParticipants
);

const { encryptedResponses, errors } = await this.porter.cbdDecrypt(
encryptedRequests,
this.threshold
);

// TODO: How many errors are acceptable? Less than (threshold - shares)?
// TODO: If Porter accepts only `threshold` decryption requests, then we may not have any errors
if (Object.keys(errors).length > 0) {
throw new Error(
`CBD decryption failed with errors: ${JSON.stringify(errors)}`
Expand Down Expand Up @@ -160,10 +167,9 @@ export class CbdTDecDecrypter {
const sharedSecrets: Record<string, SessionSharedSecret> =
Object.fromEntries(
dkgParticipants.map(({ provider, decryptionRequestStaticKey }) => {
const decKey = SessionStaticKey.fromBytes(
fromHexString(decryptionRequestStaticKey)
const sharedSecret = ephemeralSessionKey.deriveSharedSecret(
decryptionRequestStaticKey
);
const sharedSecret = ephemeralSessionKey.deriveSharedSecret(decKey);
return [provider, sharedSecret];
})
);
Expand Down
6 changes: 5 additions & 1 deletion src/characters/enrico.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ export class Enrico {
withConditions = this.conditions;
}

if (!withConditions) {
throw new Error('Conditions are required for CBD encryption.');
}

if (!(this.encryptingKey instanceof DkgPublicKey)) {
throw new Error('Wrong key type. Use encryptMessagePre instead.');
}

const aad = toBytes(withConditions?.toJson() ?? '');
const aad = withConditions.asAad();
const ciphertext = ferveoEncrypt(
plaintext instanceof Uint8Array ? plaintext : toBytes(plaintext),
aad,
Expand Down
21 changes: 17 additions & 4 deletions src/characters/porter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Base64EncodedBytes, ChecksumAddress, HexEncodedBytes } from '../types';
import { fromBase64, fromHexString, toBase64, toHexString } from '../utils';

// /get_ursulas

export type Ursula = {
readonly checksumAddress: ChecksumAddress;
readonly uri: string;
Expand All @@ -40,6 +41,7 @@ export type GetUrsulasResult = {
};

// /retrieve_cfrags

type PostRetrieveCFragsRequest = {
readonly treasure_map: Base64EncodedBytes;
readonly retrieval_kits: readonly Base64EncodedBytes[];
Expand Down Expand Up @@ -79,8 +81,15 @@ type PostCbdDecryptRequest = {
};

type PostCbdDecryptResponse = {
encrypted_decryption_responses: Record<ChecksumAddress, Base64EncodedBytes>;
errors: Record<ChecksumAddress, string>;
result: {
decryption_results: {
encrypted_decryption_responses: Record<
ChecksumAddress,
Base64EncodedBytes
>;
errors: Record<ChecksumAddress, string>;
};
};
};

export type CbdDecryptResult = {
Expand Down Expand Up @@ -174,8 +183,12 @@ export class Porter {
new URL('/cbd_decrypt', this.porterUrl).toString(),
data
);

const { encrypted_decryption_responses, errors } =
resp.data.result.decryption_results;

const decryptionResponses = Object.entries(
resp.data.encrypted_decryption_responses
encrypted_decryption_responses
).map(([address, encryptedResponseBase64]) => {
const encryptedResponse = EncryptedThresholdDecryptionResponse.fromBytes(
fromBase64(encryptedResponseBase64)
Expand All @@ -186,6 +199,6 @@ export class Porter {
string,
EncryptedThresholdDecryptionResponse
> = Object.fromEntries(decryptionResponses);
return { encryptedResponses, errors: resp.data.errors };
return { encryptedResponses, errors };
}
}
2 changes: 1 addition & 1 deletion src/conditions/compound-condition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Joi from 'joi';

import { Condition } from './base/condition';
import { Condition } from './base';
import { contractConditionSchema } from './base/contract';
import { rpcConditionSchema } from './base/rpc';
import { timeConditionSchema } from './base/time';
Expand Down
6 changes: 5 additions & 1 deletion src/conditions/condition-expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Conditions as WASMConditions } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import { SemVer } from 'semver';

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

import {
Condition,
Expand Down Expand Up @@ -90,6 +90,10 @@ export class ConditionExpression {
return new ConditionContext([this.condition], provider);
}

public asAad(): Uint8Array {
return toBytes(this.toJson());
}

public equals(other: ConditionExpression): boolean {
return (
this.version === other.version &&
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 @@ -2,7 +2,7 @@ import { Conditions as WASMConditions } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';

import { fromJSON, toJSON } from '../../utils';
import { Condition } from '../base/condition';
import { Condition } from '../base';
import { USER_ADDRESS_PARAM } from '../const';

import { TypedSignature, WalletAuthenticationProvider } from './providers';
Expand Down
37 changes: 15 additions & 22 deletions src/dkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@nucypher/nucypher-core';
import { ethers } from 'ethers';

import { DkgCoordinatorAgent } from './agents/coordinator';
import { bytesEquals, fromHexString } from './utils';

// TODO: Expose from @nucypher/nucypher-core
Expand Down Expand Up @@ -77,38 +78,30 @@ export class DkgRitual {
return (
this.id === other.id &&
// TODO: Replace with `equals` after https://github.com/nucypher/nucypher-core/issues/56 is fixed
bytesEquals(this.dkgPublicKey.toBytes(), other.dkgPublicKey.toBytes())
bytesEquals(this.dkgPublicKey.toBytes(), other.dkgPublicKey.toBytes()) &&
this.threshold === other.threshold
);
}
}

export class DkgClient {
constructor(private readonly provider: ethers.providers.Web3Provider) {}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public async initializeRitual(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_provider: ethers.providers.Web3Provider,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ritualParams: unknown
): Promise<DkgRitual> {
// TODO: Remove this check after implementing this method
if (!this.provider._isProvider) {
throw new Error('Invalid provider');
}
// TODO: Create a new DKG ritual here
const pkWord1 = fromHexString(
'9045795411ed251bf2eecc9415552c41863502a207104ef7ab482bc2364729d9'
);
console.assert(pkWord1.length === 32);
const pkWord2 = fromHexString('b99e2949cee8d888663b2995fc647fcf');
// We need to concat two words returned by the DKG contract
const dkgPkBytes = new Uint8Array([...pkWord1, ...pkWord2]);
console.assert(dkgPkBytes.length === 48);
public async initializeRitual(ritualParams: {
shares: number;
threshold: number;
}): Promise<DkgRitual> {
const ritualId = 2;
const ritual = await DkgCoordinatorAgent.getRitual(this.provider, ritualId);
const dkgPkBytes = new Uint8Array([
...fromHexString(ritual.publicKey.word0),
...fromHexString(ritual.publicKey.word1),
]);

return {
id: 0,
id: ritualId,
dkgPublicKey: DkgPublicKey.fromBytes(dkgPkBytes),
threshold: ritualParams.threshold,
} as DkgRitual;
}

Expand Down
5 changes: 1 addition & 4 deletions src/sdk/strategy/cbd-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ export class CbdStrategy {
shares: this.cohort.configuration.shares,
};
const dkgClient = new DkgClient(provider);
const dkgRitual = await dkgClient.initializeRitual(
provider,
dkgRitualParams
);
const dkgRitual = await dkgClient.initializeRitual(dkgRitualParams);
return DeployedCbdStrategy.create(this.cohort, dkgRitual);
}

Expand Down
4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import deepEqual from 'deep-equal';

// TODO: Replace byte and hex manipulation with ethers.js
export const toBytes = (str: string): Uint8Array =>
new TextEncoder().encode(str);

export const fromHexString = (hexString: string): Uint8Array => {
if (hexString.startsWith('0x')) {
hexString = hexString.slice(2);
}
const matches = hexString.match(/.{1,2}/g) ?? [];
return new Uint8Array(matches.map((byte) => parseInt(byte, 16)));
};
Expand Down
10 changes: 3 additions & 7 deletions test/unit/cbd-strategy.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { SecretKey, SessionStaticSecret } from '@nucypher/nucypher-core';

import { conditions } from '../../src';
import { FerveoVariant } from '../../src';
import { CbdStrategy, DeployedCbdStrategy } from '../../src';
import { CbdTDecDecrypter } from '../../src/characters/cbd-recipient';
import { FerveoVariant } from '../../src/dkg';
import {
CbdStrategy,
DeployedCbdStrategy,
} from '../../src/sdk/strategy/cbd-strategy';
import { toBytes } from '../../src/utils';
import {
fakeDkgFlow,
Expand Down Expand Up @@ -136,8 +133,7 @@ describe('CbdDeployedStrategy', () => {
aliceProvider,
conditionExpr,
variant,
ciphertext,
aad
ciphertext
);
expect(getUrsulasSpy).toHaveBeenCalled();
expect(getParticipantsSpy).toHaveBeenCalled();
Expand Down
Loading

0 comments on commit a8aaf8e

Please sign in to comment.