Skip to content

Commit

Permalink
Use e2e-encrypted tDec requests (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Jun 14, 2023
2 parents 2b68d2c + e23b0cb commit 989e6d5
Show file tree
Hide file tree
Showing 10 changed files with 557 additions and 272 deletions.
193 changes: 124 additions & 69 deletions abi/Coordinator.json

Large diffs are not rendered by default.

13 changes: 3 additions & 10 deletions src/agents/coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,22 @@ import {
Coordinator,
Coordinator__factory,
} from '../../types/ethers-contracts';
import { BLS12381 } from '../../types/ethers-contracts/Coordinator';

import { getContract } from './contracts';

export interface CoordinatorRitual {
id: number;
initiator: string;
dkgSize: number;
initTimestamp: number;
totalTranscripts: number;
totalAggregations: number;
aggregatedTranscriptHash: string;
publicKey: BLS12381.G1PointStructOutput;
aggregationMismatch: boolean;
aggregatedTranscript: string;
publicKey: string;
publicKeyHash: string;
}

export interface DkgParticipant {
node: string;
aggregated: boolean;
transcript: string;
publicKey: string;
}
export type DkgParticipant = Coordinator.ParticipantStructOutput;

export class DkgCoordinatorAgent {
public static async getParticipants(
Expand Down
166 changes: 126 additions & 40 deletions src/characters/cbd-recipient.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
import {
Ciphertext,
combineDecryptionSharesPrecomputed,
combineDecryptionSharesSimple,
Context,
DecryptionSharePrecomputed,
DecryptionShareSimple,
decryptWithSharedSecret,
SharedSecret,
EncryptedThresholdDecryptionRequest,
EncryptedThresholdDecryptionResponse,
SessionSharedSecret,
SessionStaticKey,
SessionStaticSecret,
ThresholdDecryptionRequest,
} from '@nucypher/nucypher-core';
import { ethers } from 'ethers';

import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator';
import { ConditionSet } from '../conditions';
import { DkgRitual, FerveoVariant } from '../dkg';
import { fromJSON, toJSON } from '../utils';
import {
DkgRitual,
getCombineDecryptionSharesFunction,
getVariantClass,
} from '../dkg';
import { fromHexString, fromJSON, toJSON } from '../utils';

import { Porter } from './porter';

type CbdTDecDecrypterJSON = {
export type CbdTDecDecrypterJSON = {
porterUri: string;
threshold: number;
};

export class CbdTDecDecrypter {
private readonly porter: Porter;

// private readonly verifyingKey: Keyring;

constructor(porterUri: string) {
constructor(porterUri: string, private readonly threshold: number) {
this.porter = new Porter(porterUri);
}

// Retrieve and decrypt ciphertext using provider and condition set
public async retrieveAndDecrypt(
provider: ethers.providers.Web3Provider,
conditionSet: ConditionSet,
Expand All @@ -46,19 +53,9 @@ export class CbdTDecDecrypter {
ciphertext
);

// TODO: Replace with a factory method
let sharedSecret: SharedSecret;
if (variant === FerveoVariant.Simple) {
sharedSecret = combineDecryptionSharesSimple(
decryptionShares as DecryptionShareSimple[]
);
} else if (variant === FerveoVariant.Precomputed) {
sharedSecret = combineDecryptionSharesPrecomputed(
decryptionShares as DecryptionSharePrecomputed[]
);
} else {
throw new Error(`Unknown variant ${variant}`);
}
const combineDecryptionSharesFn =
getCombineDecryptionSharesFunction(variant);
const sharedSecret = combineDecryptionSharesFn(decryptionShares);

const plaintext = decryptWithSharedSecret(
ciphertext,
Expand All @@ -69,53 +66,142 @@ export class CbdTDecDecrypter {
return [plaintext];
}

// Retrieve decryption shares
public async retrieve(
provider: ethers.providers.Web3Provider,
conditionSet: ConditionSet,
ritualId: number,
variant: number,
ciphertext: Ciphertext
): Promise<DecryptionSharePrecomputed[] | DecryptionShareSimple[]> {
const dkgParticipants = await DkgCoordinatorAgent.getParticipants(
provider,
ritualId
);
const contextStr = await conditionSet.buildContext(provider).toJson();
const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests(
ritualId,
variant,
ciphertext,
conditionSet,
contextStr,
dkgParticipants
);

const { encryptedResponses, errors } = await this.porter.cbdDecrypt(
encryptedRequests,
this.threshold
);
// TODO: How many errors are acceptable? Less than (threshold - shares)?
if (Object.keys(errors).length > 0) {
throw new Error(
`CBD decryption failed with errors: ${JSON.stringify(errors)}`
);
}
return this.makeDecryptionShares(
encryptedResponses,
sharedSecrets,
variant,
ritualId
);
}

private makeDecryptionShares(
encryptedResponses: Record<string, EncryptedThresholdDecryptionResponse>,
sessionSharedSecret: Record<string, SessionSharedSecret>,
variant: number,
expectedRitualId: number
) {
const decryptedResponses = Object.entries(encryptedResponses).map(
([ursula, encryptedResponse]) =>
encryptedResponse.decrypt(sessionSharedSecret[ursula])
);

const ritualIds = decryptedResponses.map(({ ritualId }) => ritualId);
if (ritualIds.some((ritualId) => ritualId !== expectedRitualId)) {
throw new Error(
`Ritual id mismatch. Expected ${expectedRitualId}, got ${ritualIds}`
);
}

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

// TODO: Move ThresholdDecryptionRequest creation and parsing to Porter?
const tDecRequest = new ThresholdDecryptionRequest(
const DecryptionShareType = getVariantClass(variant);
return decryptionShares.map((share) =>
DecryptionShareType.fromBytes(share)
);
}

private makeDecryptionRequests(
ritualId: number,
variant: number,
ciphertext: Ciphertext,
conditionSet: ConditionSet,
contextStr: string,
dkgParticipants: Array<DkgParticipant>
): {
sharedSecrets: Record<string, SessionSharedSecret>;
encryptedRequests: Record<string, EncryptedThresholdDecryptionRequest>;
} {
const decryptionRequest = new ThresholdDecryptionRequest(
ritualId,
variant,
ciphertext,
conditionSet.toWASMConditions(),
new Context(contextStr)
);

// TODO: This should return multiple responses
const resp = await this.porter.decrypt(tDecRequest);

// TODO: Replace with a factory method
if (variant === FerveoVariant.Simple) {
return resp.map((r) =>
DecryptionShareSimple.fromBytes(r.decryptionShare)
const ephemeralSessionKey = this.makeSessionKey();

// Compute shared secrets for each participant
const sharedSecrets: Record<string, SessionSharedSecret> =
Object.fromEntries(
dkgParticipants.map(({ provider, decryptionRequestStaticKey }) => {
const decKey = SessionStaticKey.fromBytes(
fromHexString(decryptionRequestStaticKey)
);
const sharedSecret = ephemeralSessionKey.deriveSharedSecret(decKey);
return [provider, sharedSecret];
})
);
} else if (variant === FerveoVariant.Precomputed) {
return resp.map((r) =>
DecryptionSharePrecomputed.fromBytes(r.decryptionShare)
);
} else {
throw new Error(`Unknown variant ${variant}`);
}

// Create encrypted requests for each participant
const encryptedRequests: Record<
string,
EncryptedThresholdDecryptionRequest
> = Object.fromEntries(
Object.entries(sharedSecrets).map(([provider, sessionSharedSecret]) => {
const encryptedRequest = decryptionRequest.encrypt(
sessionSharedSecret,
ephemeralSessionKey.publicKey()
);
return [provider, encryptedRequest];
})
);

return { sharedSecrets, encryptedRequests };
}

private makeSessionKey() {
// Moving to a separate function to make it easier to mock
return SessionStaticSecret.random();
}

public toObj(): CbdTDecDecrypterJSON {
return {
porterUri: this.porter.porterUrl.toString(),
threshold: this.threshold,
};
}

public toJSON(): string {
return toJSON(this.toObj());
}

public static fromObj({ porterUri }: CbdTDecDecrypterJSON) {
return new CbdTDecDecrypter(porterUri);
public static fromObj({ porterUri, threshold }: CbdTDecDecrypterJSON) {
return new CbdTDecDecrypter(porterUri, threshold);
}

public static fromJSON(json: string) {
Expand Down
Loading

1 comment on commit 989e6d5

@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/types/ethers-contracts/factories: 82.03 KB
build/module/types/ethers-contracts: 156.25 KB
build/module/types: 160.16 KB
build/module/src/agents: 31.25 KB
build/module/src/policies: 19.53 KB
build/module/src/conditions/base: 50.78 KB
build/module/src/conditions/predefined: 19.53 KB
build/module/src/conditions/context: 39.06 KB
build/module/src/conditions: 148.44 KB
build/module/src/sdk/strategy: 42.97 KB
build/module/src/sdk: 58.59 KB
build/module/src/characters: 89.84 KB
build/module/src/kits: 19.53 KB
build/module/src: 433.59 KB
build/module: 644.53 KB
build/main/types/ethers-contracts/factories: 82.03 KB
build/main/types/ethers-contracts: 156.25 KB
build/main/types: 160.16 KB
build/main/src/agents: 31.25 KB
build/main/src/policies: 19.53 KB
build/main/src/conditions/base: 50.78 KB
build/main/src/conditions/predefined: 19.53 KB
build/main/src/conditions/context: 39.06 KB
build/main/src/conditions: 148.44 KB
build/main/src/sdk/strategy: 42.97 KB
build/main/src/sdk: 58.59 KB
build/main/src/characters: 89.84 KB
build/main/src/kits: 19.53 KB
build/main/src: 437.50 KB
build/main: 648.44 KB
build: 1.27 MB

Please sign in to comment.