From f78a55fb362c5c2eccc07f34d35ab89cc34118f7 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 9 Jun 2023 12:24:55 +0200
Subject: [PATCH 1/6] feat: use e2e-encrypted decryption requests
---
src/characters/cbd-recipient.ts | 109 ++++++++++++++++++++++++++-----
src/characters/porter.ts | 77 ++++++++++++++++------
src/sdk/strategy/cbd-strategy.ts | 12 +++-
test/unit/cbd-strategy.test.ts | 8 ++-
test/utils.ts | 43 +++++++++---
5 files changed, 198 insertions(+), 51 deletions(-)
diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts
index 9918da54a..8ccaaf919 100644
--- a/src/characters/cbd-recipient.ts
+++ b/src/characters/cbd-recipient.ts
@@ -6,6 +6,8 @@ import {
DecryptionSharePrecomputed,
DecryptionShareSimple,
decryptWithSharedSecret,
+ SessionSecretFactory,
+ SessionSharedSecret,
SharedSecret,
ThresholdDecryptionRequest,
} from '@nucypher/nucypher-core';
@@ -13,12 +15,15 @@ import { ethers } from 'ethers';
import { ConditionSet } from '../conditions';
import { DkgRitual, FerveoVariant } from '../dkg';
-import { fromJSON, toJSON } from '../utils';
+import { ChecksumAddress } from '../types';
+import { fromJSON, toBytes, toJSON } from '../utils';
-import { Porter } from './porter';
+import { CbdDecryptResult, Porter } from './porter';
-type CbdTDecDecrypterJSON = {
+export type CbdTDecDecrypterJSON = {
porterUri: string;
+ ursulas: Array;
+ threshold: number;
};
export class CbdTDecDecrypter {
@@ -26,7 +31,11 @@ export class CbdTDecDecrypter {
// private readonly verifyingKey: Keyring;
- constructor(porterUri: string) {
+ constructor(
+ porterUri: string,
+ private readonly ursulas: Array,
+ private readonly threshold: number
+ ) {
this.porter = new Porter(porterUri);
}
@@ -79,34 +88,94 @@ export class CbdTDecDecrypter {
const contextStr = await conditionSet.buildContext(provider).toJson();
// TODO: Move ThresholdDecryptionRequest creation and parsing to Porter?
- const tDecRequest = new ThresholdDecryptionRequest(
- ritualId,
- variant,
- ciphertext,
- conditionSet.toWASMConditions(),
- new Context(contextStr)
+ const { sessionSharedSecret, encryptedRequest } =
+ this.makeDecryptionRequest(
+ ritualId,
+ variant,
+ ciphertext,
+ conditionSet,
+ contextStr
+ );
+
+ const cbdDecryptResult = await this.porter.cbdDecrypt(
+ encryptedRequest,
+ this.ursulas,
+ this.threshold
);
- // TODO: This should return multiple responses
- const resp = await this.porter.decrypt(tDecRequest);
+ return this.makeDecryptionShares(
+ cbdDecryptResult,
+ sessionSharedSecret,
+ variant
+ );
+ }
+ private makeDecryptionShares(
+ cbdDecryptResult: CbdDecryptResult,
+ sessionSharedSecret: SessionSharedSecret,
+ variant: number
+ ) {
+ const decryptedResponses = Object.entries(
+ cbdDecryptResult.encryptedResponses
+ ).map(([, encryptedResponse]) =>
+ encryptedResponse.decrypt(sessionSharedSecret)
+ );
+ const variants = decryptedResponses.map((resp) => resp.ritualId);
+ if (variants.some((v) => v !== variant)) {
+ throw new Error('Decryption shares are not of the same variant');
+ }
+
+ const decryptionShares = decryptedResponses.map(
+ (resp) => resp.decryptionShare
+ );
// TODO: Replace with a factory method
if (variant === FerveoVariant.Simple) {
- return resp.map((r) =>
- DecryptionShareSimple.fromBytes(r.decryptionShare)
+ return decryptionShares.map((share) =>
+ DecryptionShareSimple.fromBytes(share)
);
} else if (variant === FerveoVariant.Precomputed) {
- return resp.map((r) =>
- DecryptionSharePrecomputed.fromBytes(r.decryptionShare)
+ return decryptionShares.map((share) =>
+ DecryptionSharePrecomputed.fromBytes(share)
);
} else {
throw new Error(`Unknown variant ${variant}`);
}
}
+ private makeDecryptionRequest(
+ ritualId: number,
+ variant: number,
+ ciphertext: Ciphertext,
+ conditionSet: ConditionSet,
+ contextStr: string
+ ) {
+ const decryptionRequest = new ThresholdDecryptionRequest(
+ ritualId,
+ variant,
+ ciphertext,
+ conditionSet.toWASMConditions(),
+ new Context(contextStr)
+ );
+
+ const secretFactory = SessionSecretFactory.random();
+ const label = toBytes(`${ritualId}`);
+ const ursulaPublicKey = secretFactory.makeKey(label).publicKey();
+ const requesterSecretKey = secretFactory.makeKey(label);
+ const sessionSharedSecret =
+ requesterSecretKey.deriveSharedSecret(ursulaPublicKey);
+
+ const encryptedRequest = decryptionRequest.encrypt(
+ sessionSharedSecret,
+ requesterSecretKey.publicKey()
+ );
+ return { sessionSharedSecret, encryptedRequest };
+ }
+
public toObj(): CbdTDecDecrypterJSON {
return {
porterUri: this.porter.porterUrl.toString(),
+ ursulas: this.ursulas,
+ threshold: this.threshold,
};
}
@@ -114,8 +183,12 @@ export class CbdTDecDecrypter {
return toJSON(this.toObj());
}
- public static fromObj({ porterUri }: CbdTDecDecrypterJSON) {
- return new CbdTDecDecrypter(porterUri);
+ public static fromObj({
+ porterUri,
+ ursulas,
+ threshold,
+ }: CbdTDecDecrypterJSON) {
+ return new CbdTDecDecrypter(porterUri, ursulas, threshold);
}
public static fromJSON(json: string) {
diff --git a/src/characters/porter.ts b/src/characters/porter.ts
index 47f403a8e..366e49eaa 100644
--- a/src/characters/porter.ts
+++ b/src/characters/porter.ts
@@ -1,9 +1,9 @@
import {
CapsuleFrag,
+ EncryptedThresholdDecryptionRequest,
+ EncryptedThresholdDecryptionResponse,
PublicKey,
RetrievalKit,
- ThresholdDecryptionRequest,
- ThresholdDecryptionResponse,
TreasureMap,
} from '@nucypher/nucypher-core';
import axios, { AxiosResponse } from 'axios';
@@ -13,6 +13,7 @@ import { ConditionContext } from '../conditions';
import { Base64EncodedBytes, ChecksumAddress, HexEncodedBytes } from '../types';
import { fromBase64, fromHexString, toBase64, toHexString } from '../utils';
+// /get_ursulas
export type Ursula = {
readonly checksumAddress: ChecksumAddress;
readonly uri: string;
@@ -31,13 +32,14 @@ type UrsulaResponse = {
readonly encrypting_key: HexEncodedBytes;
};
-export type GetUrsulasResponse = {
+export type GetUrsulasResult = {
readonly result: {
readonly ursulas: readonly UrsulaResponse[];
};
readonly version: string;
};
+// /retrieve_cfrags
type PostRetrieveCFragsRequest = {
readonly treasure_map: Base64EncodedBytes;
readonly retrieval_kits: readonly Base64EncodedBytes[];
@@ -47,7 +49,7 @@ type PostRetrieveCFragsRequest = {
readonly context?: string;
};
-type PostRetrieveCFragsResult = {
+type PostRetrieveCFragsResponse = {
readonly result: {
readonly retrieval_results: readonly {
readonly cfrags: {
@@ -61,14 +63,30 @@ type PostRetrieveCFragsResult = {
readonly version: string;
};
-export type RetrieveCFragsResponse = {
- cFrags: Record;
- errors: Record;
+export type RetrieveCFragsResult = {
+ readonly cFrags: Record;
+ readonly errors: Record;
};
-type PostDecryptRequest = Uint8Array;
+// /cbd_decrypt
+
+type PostCbdDecryptRequest = {
+ readonly threshold: number;
+ readonly encrypted_decryption_requests: Record<
+ ChecksumAddress,
+ Base64EncodedBytes
+ >;
+};
+
+type PostCbdDecryptResponse = {
+ encrypted_decryption_responses: Record;
+ errors: Record;
+};
-type PostDecryptResult = Uint8Array;
+export type CbdDecryptResult = {
+ encryptedResponses: Record;
+ errors: Record;
+};
export class Porter {
readonly porterUrl: URL;
@@ -87,7 +105,7 @@ export class Porter {
exclude_ursulas: excludeUrsulas,
include_ursulas: includeUrsulas,
};
- const resp: AxiosResponse = await axios.get(
+ const resp: AxiosResponse = await axios.get(
new URL('/get_ursulas', this.porterUrl).toString(),
{
params,
@@ -112,7 +130,7 @@ export class Porter {
bobEncryptingKey: PublicKey,
bobVerifyingKey: PublicKey,
conditionsContext?: ConditionContext
- ): Promise {
+ ): Promise {
const context = conditionsContext
? await conditionsContext.toJson()
: undefined;
@@ -124,7 +142,7 @@ export class Porter {
bob_verifying_key: toHexString(bobVerifyingKey.toCompressedBytes()),
context,
};
- const resp: AxiosResponse = await axios.post(
+ const resp: AxiosResponse = await axios.post(
new URL('/retrieve_cfrags', this.porterUrl).toString(),
data
);
@@ -139,15 +157,34 @@ export class Porter {
});
}
- public async decrypt(
- tDecRequest: ThresholdDecryptionRequest
- ): Promise {
- const data: PostDecryptRequest = tDecRequest.toBytes();
- const resp: AxiosResponse = await axios.post(
- new URL('/decrypt', this.porterUrl).toString(),
+ public async cbdDecrypt(
+ encryptedRequest: EncryptedThresholdDecryptionRequest,
+ ursulas: Array,
+ threshold: number
+ ): Promise {
+ const encodedEncryptedRequest = toBase64(encryptedRequest.toBytes());
+ const data: PostCbdDecryptRequest = {
+ encrypted_decryption_requests: Object.fromEntries(
+ ursulas.map((ursula) => [ursula, encodedEncryptedRequest])
+ ),
+ threshold,
+ };
+ const resp: AxiosResponse = await axios.post(
+ new URL('/cbd_decrypt', this.porterUrl).toString(),
data
);
- // TODO: In /cbd_decrypt, the response is a list of ThresholdDecryptionResponse
- return [ThresholdDecryptionResponse.fromBytes(resp.data)];
+ const decryptionResponses = Object.entries(
+ resp.data.encrypted_decryption_responses
+ ).map(([address, encryptedResponseBase64]) => {
+ const encryptedResponse = EncryptedThresholdDecryptionResponse.fromBytes(
+ fromBase64(encryptedResponseBase64)
+ );
+ return [address, encryptedResponse];
+ });
+ const encryptedResponses: Record<
+ string,
+ EncryptedThresholdDecryptionResponse
+ > = Object.fromEntries(decryptionResponses);
+ return { encryptedResponses, errors: resp.data.errors };
}
}
diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts
index 007716957..5f2729fb7 100644
--- a/src/sdk/strategy/cbd-strategy.ts
+++ b/src/sdk/strategy/cbd-strategy.ts
@@ -47,7 +47,11 @@ export class CbdStrategy {
this.conditionSet
);
- const decrypter = new CbdTDecDecrypter(this.cohort.configuration.porterUri);
+ const decrypter = new CbdTDecDecrypter(
+ this.cohort.configuration.porterUri,
+ this.cohort.ursulaAddresses,
+ this.cohort.configuration.threshold
+ );
return new DeployedCbdStrategy(
this.cohort,
@@ -122,7 +126,11 @@ export class DeployedCbdStrategy {
undefined,
maybeConditionSet
);
- const decrypter = new CbdTDecDecrypter(cohort.configuration.porterUri);
+ const decrypter = new CbdTDecDecrypter(
+ cohort.configuration.porterUri,
+ cohort.ursulaAddresses,
+ cohort.configuration.threshold
+ );
return new DeployedCbdStrategy(
cohort,
ritual,
diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts
index c58fb47d3..9cf5621e0 100644
--- a/test/unit/cbd-strategy.test.ts
+++ b/test/unit/cbd-strategy.test.ts
@@ -15,7 +15,7 @@ import {
fakeUrsulas,
fakeWeb3Provider,
makeCohort,
- mockDecrypt,
+ mockCbdDecrypt,
mockGetUrsulas,
mockInitializeRitual,
} from '../utils';
@@ -115,7 +115,11 @@ describe('CbdDeployedStrategy', () => {
ciphertext,
});
const getUrsulasSpy2 = mockGetUrsulas(mockedUrsulas);
- const decryptSpy = mockDecrypt(mockedDkg.tau, decryptionShares);
+ const decryptSpy = mockCbdDecrypt(
+ mockedDkg.tau,
+ decryptionShares,
+ mockedUrsulas.map((u) => u.checksumAddress)
+ );
const decryptedMessage =
await deployedStrategy.decrypter.retrieveAndDecrypt(
diff --git a/test/utils.ts b/test/utils.ts
index 3113bf791..b62544e55 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -5,11 +5,13 @@ import { Block } from '@ethersproject/providers';
import {
Capsule,
CapsuleFrag,
+ EncryptedThresholdDecryptionResponse,
EncryptedTreasureMap,
ferveoEncrypt,
PublicKey,
reencrypt,
SecretKey,
+ SessionSecretFactory,
ThresholdDecryptionResponse,
VerifiedCapsuleFrag,
VerifiedKeyFrag,
@@ -36,9 +38,10 @@ import { keccak256 } from 'ethers/lib/utils';
import { Alice, Bob, Cohort, Configuration, RemoteBob } from '../src';
import { CoordinatorRitual, DkgParticipant } from '../src/agents/coordinator';
import {
- GetUrsulasResponse,
+ CbdDecryptResult,
+ GetUrsulasResult,
Porter,
- RetrieveCFragsResponse,
+ RetrieveCFragsResult,
Ursula,
} from '../src/characters/porter';
import { DkgClient, DkgRitual, FerveoVariant } from '../src/dkg';
@@ -139,7 +142,7 @@ export const fakeUrsulas = (): readonly Ursula[] => {
export const mockGetUrsulas = (ursulas: readonly Ursula[]) => {
const fakePorterUrsulas = (
mockUrsulas: readonly Ursula[]
- ): GetUrsulasResponse => {
+ ): GetUrsulasResult => {
return {
result: {
ursulas: mockUrsulas.map(({ encryptingKey, uri, checksumAddress }) => ({
@@ -167,7 +170,7 @@ const fakeCFragResponse = (
ursulas: readonly ChecksumAddress[],
verifiedKFrags: readonly VerifiedKeyFrag[],
capsule: Capsule
-): readonly RetrieveCFragsResponse[] => {
+): readonly RetrieveCFragsResult[] => {
const reencrypted = verifiedKFrags
.map((kFrag) => reencrypt(capsule, kFrag))
.map((cFrag) => CapsuleFrag.fromBytes(cFrag.toBytes()));
@@ -438,14 +441,36 @@ export const fakeDkgParticipants = (): DkgParticipant[] => {
});
};
-export const mockDecrypt = (
+export const mockCbdDecrypt = (
ritualId: number,
- decryptionShares: (DecryptionSharePrecomputed | DecryptionShareSimple)[]
+ decryptionShares: (DecryptionSharePrecomputed | DecryptionShareSimple)[],
+ ursulas: ChecksumAddress[],
+ errors: Record = {}
) => {
- const result = decryptionShares.map(
- (share) => new ThresholdDecryptionResponse(ritualId, share.toBytes())
+ const encryptedResponses: Record<
+ string,
+ EncryptedThresholdDecryptionResponse
+ > = Object.fromEntries(
+ zip(decryptionShares, ursulas).map(([share, ursula]) => {
+ const secretFactory = SessionSecretFactory.random();
+ const label = toBytes(`${ritualId}`);
+ const ursulaPublicKey = secretFactory.makeKey(label).publicKey();
+ const requesterSecretKey = secretFactory.makeKey(label);
+ const sessionSharedSecret =
+ requesterSecretKey.deriveSharedSecret(ursulaPublicKey);
+
+ const resp = new ThresholdDecryptionResponse(ritualId, share.toBytes());
+ const encryptedResp = resp.encrypt(sessionSharedSecret);
+
+ return [ursula, encryptedResp];
+ })
);
- return jest.spyOn(Porter.prototype, 'decrypt').mockImplementation(() => {
+
+ const result: CbdDecryptResult = {
+ encryptedResponses,
+ errors,
+ };
+ return jest.spyOn(Porter.prototype, 'cbdDecrypt').mockImplementation(() => {
return Promise.resolve(result);
});
};
From 83716979c6232fffd5a25374e09d1e7d6d487703 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 9 Jun 2023 13:49:51 +0200
Subject: [PATCH 2/6] update coordinator contract
---
abi/Coordinator.json | 193 ++++++++++++++++++----------
src/agents/coordinator.ts | 6 +-
test/integration/dkg-client.test.ts | 2 +-
test/utils.ts | 24 +++-
4 files changed, 148 insertions(+), 77 deletions(-)
diff --git a/abi/Coordinator.json b/abi/Coordinator.json
index eb2c7f72c..604b03c6d 100644
--- a/abi/Coordinator.json
+++ b/abi/Coordinator.json
@@ -2,6 +2,11 @@
"abi": [
{
"inputs": [
+ {
+ "internalType": "contract IAccessControlApplication",
+ "name": "app",
+ "type": "address"
+ },
{
"internalType": "uint32",
"name": "_timeout",
@@ -11,11 +16,6 @@
"internalType": "uint32",
"name": "_maxDkgSize",
"type": "uint32"
- },
- {
- "internalType": "contract ApplicationInterface",
- "name": "_application",
- "type": "address"
}
],
"stateMutability": "nonpayable",
@@ -63,9 +63,9 @@
},
{
"indexed": false,
- "internalType": "enum Coordinator.RitualState",
- "name": "status",
- "type": "uint8"
+ "internalType": "bool",
+ "name": "successful",
+ "type": "bool"
}
],
"name": "EndRitual",
@@ -140,26 +140,13 @@
{
"indexed": false,
"internalType": "address[]",
- "name": "nodes",
+ "name": "participants",
"type": "address[]"
}
],
"name": "StartRitual",
"type": "event"
},
- {
- "anonymous": false,
- "inputs": [
- {
- "indexed": true,
- "internalType": "uint32",
- "name": "ritualId",
- "type": "uint32"
- }
- ],
- "name": "StartTranscriptRound",
- "type": "event"
- },
{
"anonymous": false,
"inputs": [
@@ -206,49 +193,77 @@
},
{
"inputs": [],
- "name": "PUBLIC_KEY_SIZE",
+ "name": "application",
"outputs": [
{
- "internalType": "uint256",
+ "internalType": "contract IAccessControlApplication",
"name": "",
- "type": "uint256"
+ "type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
- "inputs": [],
- "name": "applicationInterface",
+ "inputs": [
+ {
+ "internalType": "address[]",
+ "name": "nodes",
+ "type": "address[]"
+ }
+ ],
+ "name": "cohortFingerprint",
"outputs": [
{
- "internalType": "contract ApplicationInterface",
+ "internalType": "bytes32",
"name": "",
- "type": "address"
+ "type": "bytes32"
}
],
- "stateMutability": "view",
+ "stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
- "internalType": "uint32",
- "name": "ritualId",
- "type": "uint32"
+ "internalType": "uint256",
+ "name": "ritualID",
+ "type": "uint256"
},
{
"internalType": "address",
- "name": "node",
+ "name": "provider",
"type": "address"
}
],
- "name": "getNodeIndex",
+ "name": "getParticipantFromProvider",
"outputs": [
{
- "internalType": "uint256",
+ "components": [
+ {
+ "internalType": "address",
+ "name": "provider",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "aggregated",
+ "type": "bool"
+ },
+ {
+ "internalType": "bytes",
+ "name": "transcript",
+ "type": "bytes"
+ },
+ {
+ "internalType": "bytes",
+ "name": "decryptionRequestStaticKey",
+ "type": "bytes"
+ }
+ ],
+ "internalType": "struct Coordinator.Participant",
"name": "",
- "type": "uint256"
+ "type": "tuple"
}
],
"stateMutability": "view",
@@ -268,7 +283,7 @@
"components": [
{
"internalType": "address",
- "name": "node",
+ "name": "provider",
"type": "address"
},
{
@@ -280,6 +295,11 @@
"internalType": "bytes",
"name": "transcript",
"type": "bytes"
+ },
+ {
+ "internalType": "bytes",
+ "name": "decryptionRequestStaticKey",
+ "type": "bytes"
}
],
"internalType": "struct Coordinator.Participant[]",
@@ -313,7 +333,7 @@
"inputs": [
{
"internalType": "address[]",
- "name": "nodes",
+ "name": "providers",
"type": "address[]"
}
],
@@ -374,19 +394,31 @@
"name": "ritualId",
"type": "uint32"
},
- {
- "internalType": "uint256",
- "name": "nodeIndex",
- "type": "uint256"
- },
{
"internalType": "bytes",
"name": "aggregatedTranscript",
"type": "bytes"
},
{
- "internalType": "bytes",
+ "components": [
+ {
+ "internalType": "bytes32",
+ "name": "word0",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes16",
+ "name": "word1",
+ "type": "bytes16"
+ }
+ ],
+ "internalType": "struct BLS12381.G1Point",
"name": "publicKey",
+ "type": "tuple"
+ },
+ {
+ "internalType": "bytes",
+ "name": "decryptionRequestStaticKey",
"type": "bytes"
}
],
@@ -402,11 +434,6 @@
"name": "ritualId",
"type": "uint32"
},
- {
- "internalType": "uint256",
- "name": "nodeIndex",
- "type": "uint256"
- },
{
"internalType": "bytes",
"name": "transcript",
@@ -435,11 +462,6 @@
],
"name": "rituals",
"outputs": [
- {
- "internalType": "uint32",
- "name": "id",
- "type": "uint32"
- },
{
"internalType": "address",
"name": "initiator",
@@ -466,9 +488,21 @@
"type": "uint32"
},
{
- "internalType": "bytes32",
- "name": "aggregatedTranscriptHash",
- "type": "bytes32"
+ "components": [
+ {
+ "internalType": "bytes32",
+ "name": "word0",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes16",
+ "name": "word1",
+ "type": "bytes16"
+ }
+ ],
+ "internalType": "struct BLS12381.G1Point",
+ "name": "publicKey",
+ "type": "tuple"
},
{
"internalType": "bool",
@@ -479,16 +513,6 @@
"internalType": "bytes",
"name": "aggregatedTranscript",
"type": "bytes"
- },
- {
- "internalType": "bytes",
- "name": "publicKey",
- "type": "bytes"
- },
- {
- "internalType": "bytes32",
- "name": "publicKeyHash",
- "type": "bytes32"
}
],
"stateMutability": "view",
@@ -546,5 +570,36 @@
"stateMutability": "nonpayable",
"type": "function"
}
- ]
+ ],
+ "contractName": "Coordinator",
+ "deploymentBytecode": {
+ "bytecode": "0x60a06040523480156200001157600080fd5b506040516200208b3803806200208b8339810160408190526200003491620000e7565b6200003f336200007d565b6001600160a01b039092166080526002805463ffffffff938416640100000000026001600160401b031990911693909216929092171790556200013e565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b805163ffffffff81168114620000e257600080fd5b919050565b600080600060608486031215620000fd57600080fd5b83516001600160a01b03811681146200011557600080fd5b92506200012560208501620000cd565b91506200013560408501620000cd565b90509250925092565b608051611f0e6200017d600039600081816101620152818161049101528181610b0001528181610ba601528181610ed90152610f7f0152611f0e6000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c8063715018a6116100a2578063948c73ea11610071578063948c73ea1461025f5780639c48937b1461027f578063dc80d104146102a0578063f1e0ff19146102b3578063f2fde38b146102bb57600080fd5b8063715018a6146102205780638b5eeb3f146102285780638cf950611461023b5780638da5cb5b1461024e57600080fd5b80633d796abc116100de5780633d796abc146101b45780635e748733146101db5780636b75f53e146101fb57806370dea79a1461021057600080fd5b80631057de0114610110578063214b02ad1461013d57806326e4ca821461015d5780632f2eaebc1461019c575b600080fd5b61012361011e3660046116d7565b6102ce565b60405163ffffffff90911681526020015b60405180910390f35b61015061014b366004611765565b6105aa565b604051610134919061181b565b6101847f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610134565b60025461012390640100000000900463ffffffff1681565b6101c76101c236600461187d565b610785565b604051610134989796959493929190611896565b6101ee6101e936600461192a565b6108ac565b604051610134919061195a565b61020e6102093660046119b6565b610a53565b005b6002546101239063ffffffff1681565b61020e610d9c565b61020e610236366004611765565b610db0565b61020e610249366004611a09565b610e2c565b6000546001600160a01b0316610184565b61027261026d36600461187d565b611315565b6040516101349190611abf565b61029261028d3660046116d7565b611340565b604051908152602001610134565b61020e6102ae366004611765565b611373565b600154610292565b61020e6102c9366004611ae7565b6113db565b600081600281108015906102f25750600254640100000000900463ffffffff168111155b6103435760405162461bcd60e51b815260206004820152601760248201527f496e76616c6964206e756d626572206f66206e6f64657300000000000000000060448201526064015b60405180910390fd5b6001805480820182556000918252600781027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60180544263ffffffff908116600160c01b0263ffffffff60c01b19918716600160a01b0263ffffffff60a01b1933166001600160c01b03199094169390931792909217161781559091805b8481101561055257600683018054600181018255600091825260208220600390910201908989848181106103f7576103f7611b04565b905060200201602081019061040c9190611ae7565b9050806001600160a01b0316846001600160a01b03161061046f5760405162461bcd60e51b815260206004820152601860248201527f50726f766964657273206d75737420626520736f727465640000000000000000604482015260640161033a565b60405163c4903d5b60e01b81526001600160a01b0382811660048301526000917f00000000000000000000000000000000000000000000000000000000000000009091169063c4903d5b90602401602060405180830381865afa1580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe9190611b1a565b6001600160601b0316116105245760405162461bcd60e51b815260040161033a90611b43565b81546001600160a01b0319166001600160a01b0382161790915591508061054a81611b90565b9150506103c1565b50336001600160a01b03168363ffffffff167fa4e3b0c0b125669bbe2cfcbf57b6b13b68dc908d343dde9bdf0df75081ae403f8989604051610595929190611ba9565b60405180910390a35090925050505b92915050565b6060600060018363ffffffff16815481106105c7576105c7611b04565b9060005260206000209060070201905080600601805480602002602001604051908101604052809291908181526020016000905b82821015610779576000848152602090819020604080516080810182526003860290920180546001600160a01b0381168452600160a01b900460ff161515938301939093526001830180549293929184019161065690611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461068290611bf7565b80156106cf5780601f106106a4576101008083540402835291602001916106cf565b820191906000526020600020905b8154815290600101906020018083116106b257829003601f168201915b505050505081526020016002820180546106e890611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461071490611bf7565b80156107615780601f1061073657610100808354040283529160200191610761565b820191906000526020600020905b81548152906001019060200180831161074457829003601f168201915b505050505081525050815260200190600101906105fb565b50505050915050919050565b6001818154811061079557600080fd5b600091825260209182902060079190910201805460018201546040805180820190915260028401548152600384015460801b6001600160801b0319169481019490945260048301546005840180546001600160a01b0385169750600160a01b850463ffffffff90811697600160c01b8704821697600160e01b90970482169691909516949360ff1692909161082990611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461085590611bf7565b80156108a25780601f10610877576101008083540402835291602001916108a2565b820191906000526020600020905b81548152906001019060200180831161088557829003601f168201915b5050505050905088565b60408051608081018252600080825260208201526060918101829052818101919091526108f9600184815481106108e5576108e5611b04565b906000526020600020906007020183611454565b6040805160808101825282546001600160a01b0381168252600160a01b900460ff161515602082015260018301805491939284019161093790611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461096390611bf7565b80156109b05780601f10610985576101008083540402835291602001916109b0565b820191906000526020600020905b81548152906001019060200180831161099357829003601f168201915b505050505081526020016002820180546109c990611bf7565b80601f01602080910402602001604051908101604052809291908181526020018280546109f590611bf7565b8015610a425780601f10610a1757610100808354040283529160200191610a42565b820191906000526020600020905b815481529060010190602001808311610a2557829003601f168201915b505050505081525050905092915050565b600060018463ffffffff1681548110610a6e57610a6e611b04565b6000918252602090912060079091020190506001610a8b8261150a565b6005811115610a9c57610a9c611aa9565b14610ae95760405162461bcd60e51b815260206004820152601b60248201527f4e6f742077616974696e6720666f72207472616e736372697074730000000000604482015260640161033a565b60405162dca53b60e81b81523360048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dca53b0090602401602060405180830381865afa158015610b4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b739190611c31565b90506000610b818383611454565b60405163c4903d5b60e01b81526001600160a01b0384811660048301529192506000917f0000000000000000000000000000000000000000000000000000000000000000169063c4903d5b90602401602060405180830381865afa158015610bed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c119190611b1a565b6001600160601b031611610c375760405162461bcd60e51b815260040161033a90611b43565b806001018054610c4690611bf7565b159050610c955760405162461bcd60e51b815260206004820152601e60248201527f4e6f646520616c726561647920706f73746564207472616e7363726970740000604482015260640161033a565b60008585604051610ca7929190611c4e565b604051908190039020905060018201610cc1868883611cbf565b50826001600160a01b03168763ffffffff167f66568b934e848078c9787d6a66dae153eaae57f0ec3a553c11939fcdcf9c11fb83604051610d0491815260200190565b60405180910390a38354600160e01b900463ffffffff1684601c610d2783611d80565b82546101009290920a63ffffffff8181021990931691831602179091558554600160a01b81048216600160e01b909104909116039050610d935760405163ffffffff8816907fca79a3f8fdffa27f8c0a30733144db5a5bd2663f1f2561d8d665bf4da6a3dfbe90600090a25b50505050505050565b610da46115f7565b610dae6000611651565b565b610db86115f7565b6002546040805163ffffffff6401000000009093048316815291831660208301527fbb0cedd628c5ad0619627014b51dff9ab8676ce038340c26d817b8229740d0c9910160405180910390a16002805463ffffffff9092166401000000000267ffffffff0000000019909216919091179055565b600060018763ffffffff1681548110610e4757610e47611b04565b6000918252602090912060079091020190506002610e648261150a565b6005811115610e7557610e75611aa9565b14610ec25760405162461bcd60e51b815260206004820152601c60248201527f4e6f742077616974696e6720666f72206167677265676174696f6e7300000000604482015260640161033a565b60405162dca53b60e81b81523360048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dca53b0090602401602060405180830381865afa158015610f28573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4c9190611c31565b90506000610f5a8383611454565b60405163c4903d5b60e01b81526001600160a01b0384811660048301529192506000917f0000000000000000000000000000000000000000000000000000000000000000169063c4903d5b90602401602060405180830381865afa158015610fc6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fea9190611b1a565b6001600160601b0316116110105760405162461bcd60e51b815260040161033a90611b43565b8054600160a01b900460ff16156110695760405162461bcd60e51b815260206004820152601f60248201527f4e6f646520616c726561647920706f73746564206167677265676174696f6e00604482015260640161033a565b80600201805461107890611bf7565b1590506110dc5760405162461bcd60e51b815260206004820152602c60248201527f4e6f646520616c72656164792070726f7669646564207265717565737420656e60448201526b6372797074696e67206b657960a01b606482015260840161033a565b600088886040516110ee929190611c4e565b604051908190039020825460ff60a01b1916600160a01b178355905060028201611119868883611cbf565b50826001600160a01b03168a63ffffffff167f1884446739eb06b60e314b4c3b25f08b5c7377f20538c132ce7c064f38272bac8360405161115c91815260200190565b60405180910390a383600501805461117390611bf7565b90506000036111a3576005840161118b898b83611cbf565b50866002850161119b8282611db9565b90505061126e565b6040805180820190915260028501548152600385015460801b6001600160801b03191660208201526111e3906111de368a90038a018a611de8565b6116a1565b1580611207575080846005016040516111fc9190611e3e565b604051809103902014155b1561126e5760048401805460ff191660011790558354604051600081526001600160a01b039091169063ffffffff8c16907f9dc7c9243191ecf8e0264232a3cb660035e1563d41b1812f0fea00955a291a589060200160405180910390a35050505061130d565b60018401805463ffffffff1690600061128683611d80565b82546101009290920a63ffffffff81810219909316918316021790915585546001870154600160a01b90910482169116039050611308578354604051600181526001600160a01b039091169063ffffffff8c16907f9dc7c9243191ecf8e0264232a3cb660035e1563d41b1812f0fea00955a291a589060200160405180910390a35b505050505b505050505050565b60006105a46001838154811061132d5761132d611b04565b906000526020600020906007020161150a565b60008282604051602001611355929190611ba9565b60405160208183030381529060405280519060200120905092915050565b61137b6115f7565b6002546040805163ffffffff928316815291831660208301527feb65c6287031cadd2d71b59499e985dddd00f14b3a8b2ce8d951da00f29995f6910160405180910390a16002805463ffffffff191663ffffffff92909216919091179055565b6113e36115f7565b6001600160a01b0381166114485760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161033a565b61145181611651565b50565b6006820154600090815b818110156114c157600085600601828154811061147d5761147d611b04565b6000918252602090912060039091020180549091506001600160a01b038087169116036114ae5792506105a4915050565b50806114b981611b90565b91505061145e565b5060405162461bcd60e51b815260206004820152601e60248201527f5061727469636970616e74206e6f742070617274206f662072697475616c0000604482015260640161033a565b805460025460009163ffffffff600160c01b909104811691839161152f911683611eb4565b90508163ffffffff16600003611549575060009392505050565b8354600185015463ffffffff600160a01b9092048216911603611570575060059392505050565b600484015460ff1615611587575060049392505050565b8063ffffffff1642111561159f575060039392505050565b835463ffffffff600160a01b82048116600160e01b9092041610156115c8575060019392505050565b8354600185015463ffffffff600160a01b9092048216911610156115f0575060029392505050565b5050919050565b6000546001600160a01b03163314610dae5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161033a565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b805182516000911480156116d0575081602001516001600160801b03191683602001516001600160801b031916145b9392505050565b600080602083850312156116ea57600080fd5b823567ffffffffffffffff8082111561170257600080fd5b818501915085601f83011261171657600080fd5b81358181111561172557600080fd5b8660208260051b850101111561173a57600080fd5b60209290920196919550909350505050565b803563ffffffff8116811461176057600080fd5b919050565b60006020828403121561177757600080fd5b6116d08261174c565b6000815180845260005b818110156117a65760208185018101518683018201520161178a565b506000602082860101526020601f19601f83011685010191505092915050565b60018060a01b03815116825260208101511515602083015260006040820151608060408501526117f96080850182611780565b9050606083015184820360608601526118128282611780565b95945050505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b8281101561187057603f1988860301845261185e8583516117c6565b94509285019290850190600101611842565b5092979650505050505050565b60006020828403121561188f57600080fd5b5035919050565b6001600160a01b038916815263ffffffff888116602080840191909152888216604084015287821660608401529086166080830152845160a08301528401516001600160801b03191660c082015282151560e0820152610120610100820181905260009061190683820185611780565b9b9a5050505050505050505050565b6001600160a01b038116811461145157600080fd5b6000806040838503121561193d57600080fd5b82359150602083013561194f81611915565b809150509250929050565b6020815260006116d060208301846117c6565b60008083601f84011261197f57600080fd5b50813567ffffffffffffffff81111561199757600080fd5b6020830191508360208285010111156119af57600080fd5b9250929050565b6000806000604084860312156119cb57600080fd5b6119d48461174c565b9250602084013567ffffffffffffffff8111156119f057600080fd5b6119fc8682870161196d565b9497909650939450505050565b60008060008060008086880360a0811215611a2357600080fd5b611a2c8861174c565b9650602088013567ffffffffffffffff80821115611a4957600080fd5b611a558b838c0161196d565b90985096508691506040603f1984011215611a6f57600080fd5b60408a01955060808a0135925080831115611a8957600080fd5b5050611a9789828a0161196d565b979a9699509497509295939492505050565b634e487b7160e01b600052602160045260246000fd5b6020810160068310611ae157634e487b7160e01b600052602160045260246000fd5b91905290565b600060208284031215611af957600080fd5b81356116d081611915565b634e487b7160e01b600052603260045260246000fd5b600060208284031215611b2c57600080fd5b81516001600160601b03811681146116d057600080fd5b60208082526018908201527f4e6f7420656e6f75676820617574686f72697a6174696f6e0000000000000000604082015260600190565b634e487b7160e01b600052601160045260246000fd5b600060018201611ba257611ba2611b7a565b5060010190565b60208082528181018390526000908460408401835b86811015611bec578235611bd181611915565b6001600160a01b031682529183019190830190600101611bbe565b509695505050505050565b600181811c90821680611c0b57607f821691505b602082108103611c2b57634e487b7160e01b600052602260045260246000fd5b50919050565b600060208284031215611c4357600080fd5b81516116d081611915565b8183823760009101908152919050565b634e487b7160e01b600052604160045260246000fd5b601f821115611cba57600081815260208120601f850160051c81016020861015611c9b5750805b601f850160051c820191505b8181101561130d57828155600101611ca7565b505050565b67ffffffffffffffff831115611cd757611cd7611c5e565b611ceb83611ce58354611bf7565b83611c74565b6000601f841160018114611d1f5760008515611d075750838201355b600019600387901b1c1916600186901b178355611d79565b600083815260209020601f19861690835b82811015611d505786850135825560209485019460019092019101611d30565b5086821015611d6d5760001960f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b600063ffffffff808316818103611d9957611d99611b7a565b6001019392505050565b6001600160801b03198116811461145157600080fd5b81358155600181016020830135611dcf81611da3565b81546001600160801b03191660809190911c1790555050565b600060408284031215611dfa57600080fd5b6040516040810181811067ffffffffffffffff82111715611e1d57611e1d611c5e565b604052823581526020830135611e3281611da3565b60208201529392505050565b6000808354611e4c81611bf7565b60018281168015611e645760018114611e7957611ea8565b60ff1984168752821515830287019450611ea8565b8760005260208060002060005b85811015611e9f5781548a820152908401908201611e86565b50505082870194505b50929695505050505050565b63ffffffff818116838216019080821115611ed157611ed1611b7a565b509291505056fea26469706673582212200eae299f80274d63d710b26d0bfdf0da4b44dce652d7d9a6bb31ed8cbe3b95c364736f6c63430008140033"
+ },
+ "devdoc": {
+ "kind": "dev",
+ "methods": {
+ "owner()": {
+ "details": "Returns the address of the current owner."
+ },
+ "renounceOwnership()": {
+ "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner."
+ },
+ "transferOwnership(address)": {
+ "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner."
+ }
+ },
+ "title": "Coordinator",
+ "version": 1
+ },
+ "runtimeBytecode": {
+ "bytecode": "0x60a06040523480156200001157600080fd5b506040516200208b3803806200208b8339810160408190526200003491620000e7565b6200003f336200007d565b6001600160a01b039092166080526002805463ffffffff938416640100000000026001600160401b031990911693909216929092171790556200013e565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b805163ffffffff81168114620000e257600080fd5b919050565b600080600060608486031215620000fd57600080fd5b83516001600160a01b03811681146200011557600080fd5b92506200012560208501620000cd565b91506200013560408501620000cd565b90509250925092565b608051611f0e6200017d600039600081816101620152818161049101528181610b0001528181610ba601528181610ed90152610f7f0152611f0e6000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c8063715018a6116100a2578063948c73ea11610071578063948c73ea1461025f5780639c48937b1461027f578063dc80d104146102a0578063f1e0ff19146102b3578063f2fde38b146102bb57600080fd5b8063715018a6146102205780638b5eeb3f146102285780638cf950611461023b5780638da5cb5b1461024e57600080fd5b80633d796abc116100de5780633d796abc146101b45780635e748733146101db5780636b75f53e146101fb57806370dea79a1461021057600080fd5b80631057de0114610110578063214b02ad1461013d57806326e4ca821461015d5780632f2eaebc1461019c575b600080fd5b61012361011e3660046116d7565b6102ce565b60405163ffffffff90911681526020015b60405180910390f35b61015061014b366004611765565b6105aa565b604051610134919061181b565b6101847f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610134565b60025461012390640100000000900463ffffffff1681565b6101c76101c236600461187d565b610785565b604051610134989796959493929190611896565b6101ee6101e936600461192a565b6108ac565b604051610134919061195a565b61020e6102093660046119b6565b610a53565b005b6002546101239063ffffffff1681565b61020e610d9c565b61020e610236366004611765565b610db0565b61020e610249366004611a09565b610e2c565b6000546001600160a01b0316610184565b61027261026d36600461187d565b611315565b6040516101349190611abf565b61029261028d3660046116d7565b611340565b604051908152602001610134565b61020e6102ae366004611765565b611373565b600154610292565b61020e6102c9366004611ae7565b6113db565b600081600281108015906102f25750600254640100000000900463ffffffff168111155b6103435760405162461bcd60e51b815260206004820152601760248201527f496e76616c6964206e756d626572206f66206e6f64657300000000000000000060448201526064015b60405180910390fd5b6001805480820182556000918252600781027fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60180544263ffffffff908116600160c01b0263ffffffff60c01b19918716600160a01b0263ffffffff60a01b1933166001600160c01b03199094169390931792909217161781559091805b8481101561055257600683018054600181018255600091825260208220600390910201908989848181106103f7576103f7611b04565b905060200201602081019061040c9190611ae7565b9050806001600160a01b0316846001600160a01b03161061046f5760405162461bcd60e51b815260206004820152601860248201527f50726f766964657273206d75737420626520736f727465640000000000000000604482015260640161033a565b60405163c4903d5b60e01b81526001600160a01b0382811660048301526000917f00000000000000000000000000000000000000000000000000000000000000009091169063c4903d5b90602401602060405180830381865afa1580156104da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fe9190611b1a565b6001600160601b0316116105245760405162461bcd60e51b815260040161033a90611b43565b81546001600160a01b0319166001600160a01b0382161790915591508061054a81611b90565b9150506103c1565b50336001600160a01b03168363ffffffff167fa4e3b0c0b125669bbe2cfcbf57b6b13b68dc908d343dde9bdf0df75081ae403f8989604051610595929190611ba9565b60405180910390a35090925050505b92915050565b6060600060018363ffffffff16815481106105c7576105c7611b04565b9060005260206000209060070201905080600601805480602002602001604051908101604052809291908181526020016000905b82821015610779576000848152602090819020604080516080810182526003860290920180546001600160a01b0381168452600160a01b900460ff161515938301939093526001830180549293929184019161065690611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461068290611bf7565b80156106cf5780601f106106a4576101008083540402835291602001916106cf565b820191906000526020600020905b8154815290600101906020018083116106b257829003601f168201915b505050505081526020016002820180546106e890611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461071490611bf7565b80156107615780601f1061073657610100808354040283529160200191610761565b820191906000526020600020905b81548152906001019060200180831161074457829003601f168201915b505050505081525050815260200190600101906105fb565b50505050915050919050565b6001818154811061079557600080fd5b600091825260209182902060079190910201805460018201546040805180820190915260028401548152600384015460801b6001600160801b0319169481019490945260048301546005840180546001600160a01b0385169750600160a01b850463ffffffff90811697600160c01b8704821697600160e01b90970482169691909516949360ff1692909161082990611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461085590611bf7565b80156108a25780601f10610877576101008083540402835291602001916108a2565b820191906000526020600020905b81548152906001019060200180831161088557829003601f168201915b5050505050905088565b60408051608081018252600080825260208201526060918101829052818101919091526108f9600184815481106108e5576108e5611b04565b906000526020600020906007020183611454565b6040805160808101825282546001600160a01b0381168252600160a01b900460ff161515602082015260018301805491939284019161093790611bf7565b80601f016020809104026020016040519081016040528092919081815260200182805461096390611bf7565b80156109b05780601f10610985576101008083540402835291602001916109b0565b820191906000526020600020905b81548152906001019060200180831161099357829003601f168201915b505050505081526020016002820180546109c990611bf7565b80601f01602080910402602001604051908101604052809291908181526020018280546109f590611bf7565b8015610a425780601f10610a1757610100808354040283529160200191610a42565b820191906000526020600020905b815481529060010190602001808311610a2557829003601f168201915b505050505081525050905092915050565b600060018463ffffffff1681548110610a6e57610a6e611b04565b6000918252602090912060079091020190506001610a8b8261150a565b6005811115610a9c57610a9c611aa9565b14610ae95760405162461bcd60e51b815260206004820152601b60248201527f4e6f742077616974696e6720666f72207472616e736372697074730000000000604482015260640161033a565b60405162dca53b60e81b81523360048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dca53b0090602401602060405180830381865afa158015610b4f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b739190611c31565b90506000610b818383611454565b60405163c4903d5b60e01b81526001600160a01b0384811660048301529192506000917f0000000000000000000000000000000000000000000000000000000000000000169063c4903d5b90602401602060405180830381865afa158015610bed573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c119190611b1a565b6001600160601b031611610c375760405162461bcd60e51b815260040161033a90611b43565b806001018054610c4690611bf7565b159050610c955760405162461bcd60e51b815260206004820152601e60248201527f4e6f646520616c726561647920706f73746564207472616e7363726970740000604482015260640161033a565b60008585604051610ca7929190611c4e565b604051908190039020905060018201610cc1868883611cbf565b50826001600160a01b03168763ffffffff167f66568b934e848078c9787d6a66dae153eaae57f0ec3a553c11939fcdcf9c11fb83604051610d0491815260200190565b60405180910390a38354600160e01b900463ffffffff1684601c610d2783611d80565b82546101009290920a63ffffffff8181021990931691831602179091558554600160a01b81048216600160e01b909104909116039050610d935760405163ffffffff8816907fca79a3f8fdffa27f8c0a30733144db5a5bd2663f1f2561d8d665bf4da6a3dfbe90600090a25b50505050505050565b610da46115f7565b610dae6000611651565b565b610db86115f7565b6002546040805163ffffffff6401000000009093048316815291831660208301527fbb0cedd628c5ad0619627014b51dff9ab8676ce038340c26d817b8229740d0c9910160405180910390a16002805463ffffffff9092166401000000000267ffffffff0000000019909216919091179055565b600060018763ffffffff1681548110610e4757610e47611b04565b6000918252602090912060079091020190506002610e648261150a565b6005811115610e7557610e75611aa9565b14610ec25760405162461bcd60e51b815260206004820152601c60248201527f4e6f742077616974696e6720666f72206167677265676174696f6e7300000000604482015260640161033a565b60405162dca53b60e81b81523360048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063dca53b0090602401602060405180830381865afa158015610f28573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4c9190611c31565b90506000610f5a8383611454565b60405163c4903d5b60e01b81526001600160a01b0384811660048301529192506000917f0000000000000000000000000000000000000000000000000000000000000000169063c4903d5b90602401602060405180830381865afa158015610fc6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fea9190611b1a565b6001600160601b0316116110105760405162461bcd60e51b815260040161033a90611b43565b8054600160a01b900460ff16156110695760405162461bcd60e51b815260206004820152601f60248201527f4e6f646520616c726561647920706f73746564206167677265676174696f6e00604482015260640161033a565b80600201805461107890611bf7565b1590506110dc5760405162461bcd60e51b815260206004820152602c60248201527f4e6f646520616c72656164792070726f7669646564207265717565737420656e60448201526b6372797074696e67206b657960a01b606482015260840161033a565b600088886040516110ee929190611c4e565b604051908190039020825460ff60a01b1916600160a01b178355905060028201611119868883611cbf565b50826001600160a01b03168a63ffffffff167f1884446739eb06b60e314b4c3b25f08b5c7377f20538c132ce7c064f38272bac8360405161115c91815260200190565b60405180910390a383600501805461117390611bf7565b90506000036111a3576005840161118b898b83611cbf565b50866002850161119b8282611db9565b90505061126e565b6040805180820190915260028501548152600385015460801b6001600160801b03191660208201526111e3906111de368a90038a018a611de8565b6116a1565b1580611207575080846005016040516111fc9190611e3e565b604051809103902014155b1561126e5760048401805460ff191660011790558354604051600081526001600160a01b039091169063ffffffff8c16907f9dc7c9243191ecf8e0264232a3cb660035e1563d41b1812f0fea00955a291a589060200160405180910390a35050505061130d565b60018401805463ffffffff1690600061128683611d80565b82546101009290920a63ffffffff81810219909316918316021790915585546001870154600160a01b90910482169116039050611308578354604051600181526001600160a01b039091169063ffffffff8c16907f9dc7c9243191ecf8e0264232a3cb660035e1563d41b1812f0fea00955a291a589060200160405180910390a35b505050505b505050505050565b60006105a46001838154811061132d5761132d611b04565b906000526020600020906007020161150a565b60008282604051602001611355929190611ba9565b60405160208183030381529060405280519060200120905092915050565b61137b6115f7565b6002546040805163ffffffff928316815291831660208301527feb65c6287031cadd2d71b59499e985dddd00f14b3a8b2ce8d951da00f29995f6910160405180910390a16002805463ffffffff191663ffffffff92909216919091179055565b6113e36115f7565b6001600160a01b0381166114485760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161033a565b61145181611651565b50565b6006820154600090815b818110156114c157600085600601828154811061147d5761147d611b04565b6000918252602090912060039091020180549091506001600160a01b038087169116036114ae5792506105a4915050565b50806114b981611b90565b91505061145e565b5060405162461bcd60e51b815260206004820152601e60248201527f5061727469636970616e74206e6f742070617274206f662072697475616c0000604482015260640161033a565b805460025460009163ffffffff600160c01b909104811691839161152f911683611eb4565b90508163ffffffff16600003611549575060009392505050565b8354600185015463ffffffff600160a01b9092048216911603611570575060059392505050565b600484015460ff1615611587575060049392505050565b8063ffffffff1642111561159f575060039392505050565b835463ffffffff600160a01b82048116600160e01b9092041610156115c8575060019392505050565b8354600185015463ffffffff600160a01b9092048216911610156115f0575060029392505050565b5050919050565b6000546001600160a01b03163314610dae5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161033a565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b805182516000911480156116d0575081602001516001600160801b03191683602001516001600160801b031916145b9392505050565b600080602083850312156116ea57600080fd5b823567ffffffffffffffff8082111561170257600080fd5b818501915085601f83011261171657600080fd5b81358181111561172557600080fd5b8660208260051b850101111561173a57600080fd5b60209290920196919550909350505050565b803563ffffffff8116811461176057600080fd5b919050565b60006020828403121561177757600080fd5b6116d08261174c565b6000815180845260005b818110156117a65760208185018101518683018201520161178a565b506000602082860101526020601f19601f83011685010191505092915050565b60018060a01b03815116825260208101511515602083015260006040820151608060408501526117f96080850182611780565b9050606083015184820360608601526118128282611780565b95945050505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b8281101561187057603f1988860301845261185e8583516117c6565b94509285019290850190600101611842565b5092979650505050505050565b60006020828403121561188f57600080fd5b5035919050565b6001600160a01b038916815263ffffffff888116602080840191909152888216604084015287821660608401529086166080830152845160a08301528401516001600160801b03191660c082015282151560e0820152610120610100820181905260009061190683820185611780565b9b9a5050505050505050505050565b6001600160a01b038116811461145157600080fd5b6000806040838503121561193d57600080fd5b82359150602083013561194f81611915565b809150509250929050565b6020815260006116d060208301846117c6565b60008083601f84011261197f57600080fd5b50813567ffffffffffffffff81111561199757600080fd5b6020830191508360208285010111156119af57600080fd5b9250929050565b6000806000604084860312156119cb57600080fd5b6119d48461174c565b9250602084013567ffffffffffffffff8111156119f057600080fd5b6119fc8682870161196d565b9497909650939450505050565b60008060008060008086880360a0811215611a2357600080fd5b611a2c8861174c565b9650602088013567ffffffffffffffff80821115611a4957600080fd5b611a558b838c0161196d565b90985096508691506040603f1984011215611a6f57600080fd5b60408a01955060808a0135925080831115611a8957600080fd5b5050611a9789828a0161196d565b979a9699509497509295939492505050565b634e487b7160e01b600052602160045260246000fd5b6020810160068310611ae157634e487b7160e01b600052602160045260246000fd5b91905290565b600060208284031215611af957600080fd5b81356116d081611915565b634e487b7160e01b600052603260045260246000fd5b600060208284031215611b2c57600080fd5b81516001600160601b03811681146116d057600080fd5b60208082526018908201527f4e6f7420656e6f75676820617574686f72697a6174696f6e0000000000000000604082015260600190565b634e487b7160e01b600052601160045260246000fd5b600060018201611ba257611ba2611b7a565b5060010190565b60208082528181018390526000908460408401835b86811015611bec578235611bd181611915565b6001600160a01b031682529183019190830190600101611bbe565b509695505050505050565b600181811c90821680611c0b57607f821691505b602082108103611c2b57634e487b7160e01b600052602260045260246000fd5b50919050565b600060208284031215611c4357600080fd5b81516116d081611915565b8183823760009101908152919050565b634e487b7160e01b600052604160045260246000fd5b601f821115611cba57600081815260208120601f850160051c81016020861015611c9b5750805b601f850160051c820191505b8181101561130d57828155600101611ca7565b505050565b67ffffffffffffffff831115611cd757611cd7611c5e565b611ceb83611ce58354611bf7565b83611c74565b6000601f841160018114611d1f5760008515611d075750838201355b600019600387901b1c1916600186901b178355611d79565b600083815260209020601f19861690835b82811015611d505786850135825560209485019460019092019101611d30565b5086821015611d6d5760001960f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b600063ffffffff808316818103611d9957611d99611b7a565b6001019392505050565b6001600160801b03198116811461145157600080fd5b81358155600181016020830135611dcf81611da3565b81546001600160801b03191660809190911c1790555050565b600060408284031215611dfa57600080fd5b6040516040810181811067ffffffffffffffff82111715611e1d57611e1d611c5e565b604052823581526020830135611e3281611da3565b60208201529392505050565b6000808354611e4c81611bf7565b60018281168015611e645760018114611e7957611ea8565b60ff1984168752821515830287019450611ea8565b8760005260208060002060005b85811015611e9f5781548a820152908401908201611e86565b50505082870194505b50929695505050505050565b63ffffffff818116838216019080821115611ed157611ed1611b7a565b509291505056fea26469706673582212200eae299f80274d63d710b26d0bfdf0da4b44dce652d7d9a6bb31ed8cbe3b95c364736f6c63430008140033"
+ },
+ "sourceId": "contracts/coordination/Coordinator.sol",
+ "sourcemap": "282:9240:36:-:0;;;1917:176;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;936:32:17;719:10:24;936:18:17;:32::i;:::-;-1:-1:-1;;;;;2007:17:36;;;;;2034:7;:18;;;2062:24;;;;;-1:-1:-1;;;;;;2062:24:36;;;2034:18;;;;2062:24;;;;;;;282:9240;;2433:187:17;2506:16;2525:6;;-1:-1:-1;;;;;2541:17:17;;;-1:-1:-1;;;;;;2541:17:17;;;;;;2573:40;;2525:6;;;;;;;2573:40;;2506:16;2573:40;2496:124;2433:187;:::o;14:167:63:-;92:13;;145:10;134:22;;124:33;;114:61;;171:1;168;161:12;114:61;14:167;;;:::o;186:491::-;307:6;315;323;376:2;364:9;355:7;351:23;347:32;344:52;;;392:1;389;382:12;344:52;418:16;;-1:-1:-1;;;;;463:31:63;;453:42;;443:70;;509:1;506;499:12;443:70;532:5;-1:-1:-1;556:48:63;600:2;585:18;;556:48;:::i;:::-;546:58;;623:48;667:2;656:9;652:18;623:48;:::i;:::-;613:58;;186:491;;;;;:::o;:::-;282:9240:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
+ "userdoc": {
+ "kind": "user",
+ "methods": {},
+ "notice": "Coordination layer for DKG-TDec",
+ "version": 1
+ }
}
diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts
index f6fca8176..1c047cde5 100644
--- a/src/agents/coordinator.ts
+++ b/src/agents/coordinator.ts
@@ -4,21 +4,19 @@ 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 {
diff --git a/test/integration/dkg-client.test.ts b/test/integration/dkg-client.test.ts
index 824c30ab9..f14273979 100644
--- a/test/integration/dkg-client.test.ts
+++ b/test/integration/dkg-client.test.ts
@@ -25,7 +25,7 @@ describe('DkgCoordinatorAgent', () => {
const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
const ritual = await DkgCoordinatorAgent.getRitual(provider, ritualId);
- expect(ritual.id).toEqual(ritualId);
+ expect(ritual).toBeDefined();
});
it('fetches participants from the coordinator', async () => {
diff --git a/test/utils.ts b/test/utils.ts
index b62544e55..995835c4c 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -36,7 +36,7 @@ import { ethers, providers, Wallet } from 'ethers';
import { keccak256 } from 'ethers/lib/utils';
import { Alice, Bob, Cohort, Configuration, RemoteBob } from '../src';
-import { CoordinatorRitual, DkgParticipant } from '../src/agents/coordinator';
+import { DkgParticipant } from '../src/agents/coordinator';
import {
CbdDecryptResult,
GetUrsulasResult,
@@ -409,8 +409,23 @@ export const fakeDkgTDecFlowE2e = (
};
};
-export const fakeCoordinatorRitual = (ritualId: number): CoordinatorRitual => {
+export const fakeCoordinatorRitual = (
+ ritualId: number
+): {
+ aggregationMismatch: boolean;
+ initTimestamp: number;
+ aggregatedTranscriptHash: string;
+ initiator: string;
+ dkgSize: number;
+ id: number;
+ publicKey: { word1: string; word0: string };
+ totalTranscripts: number;
+ aggregatedTranscript: string;
+ publicKeyHash: string;
+ totalAggregations: number;
+} => {
const ritual = fakeDkgTDecFlowE2e(FerveoVariant.Precomputed);
+ const dkgPkBytes = ritual.dkg.publicKey().toBytes();
return {
id: ritualId,
initiator: ritual.validators[0].address.toString(),
@@ -421,7 +436,10 @@ export const fakeCoordinatorRitual = (ritualId: number): CoordinatorRitual => {
aggregatedTranscriptHash: keccak256(ritual.serverAggregate.toBytes()),
aggregationMismatch: false, // Assuming the ritual is correct
aggregatedTranscript: toHexString(ritual.serverAggregate.toBytes()),
- publicKey: toHexString(ritual.dkg.publicKey().toBytes()),
+ publicKey: {
+ word0: toHexString(dkgPkBytes.slice(0, 32)),
+ word1: toHexString(dkgPkBytes.slice(32, 48)),
+ },
publicKeyHash: keccak256(ritual.dkg.publicKey().toBytes()),
};
};
From a47e20879c53006d4a33c9965a240596ab49973c Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 9 Jun 2023 16:16:21 +0200
Subject: [PATCH 3/6] use coordinator provided session keys
---
src/agents/coordinator.ts | 7 +-
src/characters/cbd-recipient.ts | 111 ++++++++++++++++------------
src/characters/porter.ts | 9 ++-
src/dkg.ts | 56 +++++++-------
src/sdk/strategy/cbd-strategy.ts | 2 -
test/integration/dkg-client.test.ts | 22 +++---
test/unit/cbd-strategy.test.ts | 15 ++--
test/utils.ts | 109 +++++++++++++--------------
8 files changed, 167 insertions(+), 164 deletions(-)
diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts
index 1c047cde5..80fd82fc0 100644
--- a/src/agents/coordinator.ts
+++ b/src/agents/coordinator.ts
@@ -19,12 +19,7 @@ export interface CoordinatorRitual {
aggregatedTranscript: string;
}
-export interface DkgParticipant {
- node: string;
- aggregated: boolean;
- transcript: string;
- publicKey: string;
-}
+export type DkgParticipant = Coordinator.ParticipantStructOutput;
export class DkgCoordinatorAgent {
public static async getParticipants(
diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts
index 8ccaaf919..125b39f6b 100644
--- a/src/characters/cbd-recipient.ts
+++ b/src/characters/cbd-recipient.ts
@@ -6,23 +6,25 @@ import {
DecryptionSharePrecomputed,
DecryptionShareSimple,
decryptWithSharedSecret,
+ EncryptedThresholdDecryptionRequest,
+ EncryptedThresholdDecryptionResponse,
SessionSecretFactory,
SessionSharedSecret,
+ SessionStaticKey,
SharedSecret,
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 { ChecksumAddress } from '../types';
-import { fromJSON, toBytes, toJSON } from '../utils';
+import { fromHexString, fromJSON, toBytes, toJSON } from '../utils';
-import { CbdDecryptResult, Porter } from './porter';
+import { Porter } from './porter';
export type CbdTDecDecrypterJSON = {
porterUri: string;
- ursulas: Array;
threshold: number;
};
@@ -31,11 +33,7 @@ export class CbdTDecDecrypter {
// private readonly verifyingKey: Keyring;
- constructor(
- porterUri: string,
- private readonly ursulas: Array,
- private readonly threshold: number
- ) {
+ constructor(porterUri: string, private readonly threshold: number) {
this.porter = new Porter(porterUri);
}
@@ -87,39 +85,42 @@ export class CbdTDecDecrypter {
): Promise {
const contextStr = await conditionSet.buildContext(provider).toJson();
- // TODO: Move ThresholdDecryptionRequest creation and parsing to Porter?
- const { sessionSharedSecret, encryptedRequest } =
- this.makeDecryptionRequest(
- ritualId,
- variant,
- ciphertext,
- conditionSet,
- contextStr
- );
+ const dkgParticipants = await DkgCoordinatorAgent.getParticipants(
+ provider,
+ ritualId
+ );
+
+ const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests(
+ ritualId,
+ variant,
+ ciphertext,
+ conditionSet,
+ contextStr,
+ dkgParticipants
+ );
- const cbdDecryptResult = await this.porter.cbdDecrypt(
- encryptedRequest,
- this.ursulas,
+ const { encryptedResponses } = await this.porter.cbdDecrypt(
+ encryptedRequests,
this.threshold
);
return this.makeDecryptionShares(
- cbdDecryptResult,
- sessionSharedSecret,
+ encryptedResponses,
+ sharedSecrets,
variant
);
}
private makeDecryptionShares(
- cbdDecryptResult: CbdDecryptResult,
- sessionSharedSecret: SessionSharedSecret,
+ encryptedResponses: Record,
+ sessionSharedSecret: Record,
variant: number
) {
- const decryptedResponses = Object.entries(
- cbdDecryptResult.encryptedResponses
- ).map(([, encryptedResponse]) =>
- encryptedResponse.decrypt(sessionSharedSecret)
+ const decryptedResponses = Object.entries(encryptedResponses).map(
+ ([ursula, encryptedResponse]) =>
+ encryptedResponse.decrypt(sessionSharedSecret[ursula])
);
+
const variants = decryptedResponses.map((resp) => resp.ritualId);
if (variants.some((v) => v !== variant)) {
throw new Error('Decryption shares are not of the same variant');
@@ -142,13 +143,17 @@ export class CbdTDecDecrypter {
}
}
- private makeDecryptionRequest(
+ private makeDecryptionRequests(
ritualId: number,
variant: number,
ciphertext: Ciphertext,
conditionSet: ConditionSet,
- contextStr: string
- ) {
+ contextStr: string,
+ dkgParticipants: Array
+ ): {
+ sharedSecrets: Record;
+ encryptedRequests: Record;
+ } {
const decryptionRequest = new ThresholdDecryptionRequest(
ritualId,
variant,
@@ -156,25 +161,41 @@ export class CbdTDecDecrypter {
conditionSet.toWASMConditions(),
new Context(contextStr)
);
-
const secretFactory = SessionSecretFactory.random();
const label = toBytes(`${ritualId}`);
- const ursulaPublicKey = secretFactory.makeKey(label).publicKey();
const requesterSecretKey = secretFactory.makeKey(label);
- const sessionSharedSecret =
- requesterSecretKey.deriveSharedSecret(ursulaPublicKey);
- const encryptedRequest = decryptionRequest.encrypt(
- sessionSharedSecret,
- requesterSecretKey.publicKey()
+ const sharedSecrets: Record =
+ Object.fromEntries(
+ dkgParticipants.map((participant) => {
+ const decKey = SessionStaticKey.fromBytes(
+ fromHexString(participant.decryptionRequestStaticKey)
+ );
+ const sessionSharedSecret =
+ requesterSecretKey.deriveSharedSecret(decKey);
+ return [participant.provider, sessionSharedSecret];
+ })
+ );
+
+ const encryptedRequests: Record<
+ string,
+ EncryptedThresholdDecryptionRequest
+ > = Object.fromEntries(
+ Object.entries(sharedSecrets).map(([provider, sessionSharedSecret]) => {
+ const encryptedRequest = decryptionRequest.encrypt(
+ sessionSharedSecret,
+ requesterSecretKey.publicKey()
+ );
+ return [provider, encryptedRequest];
+ })
);
- return { sessionSharedSecret, encryptedRequest };
+
+ return { sharedSecrets, encryptedRequests };
}
public toObj(): CbdTDecDecrypterJSON {
return {
porterUri: this.porter.porterUrl.toString(),
- ursulas: this.ursulas,
threshold: this.threshold,
};
}
@@ -183,12 +204,8 @@ export class CbdTDecDecrypter {
return toJSON(this.toObj());
}
- public static fromObj({
- porterUri,
- ursulas,
- threshold,
- }: CbdTDecDecrypterJSON) {
- return new CbdTDecDecrypter(porterUri, ursulas, threshold);
+ public static fromObj({ porterUri, threshold }: CbdTDecDecrypterJSON) {
+ return new CbdTDecDecrypter(porterUri, threshold);
}
public static fromJSON(json: string) {
diff --git a/src/characters/porter.ts b/src/characters/porter.ts
index 366e49eaa..1fd05f24f 100644
--- a/src/characters/porter.ts
+++ b/src/characters/porter.ts
@@ -158,14 +158,15 @@ export class Porter {
}
public async cbdDecrypt(
- encryptedRequest: EncryptedThresholdDecryptionRequest,
- ursulas: Array,
+ encryptedRequests: Record,
threshold: number
): Promise {
- const encodedEncryptedRequest = toBase64(encryptedRequest.toBytes());
const data: PostCbdDecryptRequest = {
encrypted_decryption_requests: Object.fromEntries(
- ursulas.map((ursula) => [ursula, encodedEncryptedRequest])
+ Object.entries(encryptedRequests).map(([ursula, encryptedRequest]) => [
+ ursula,
+ toBase64(encryptedRequest.toBytes()),
+ ])
),
threshold,
};
diff --git a/src/dkg.ts b/src/dkg.ts
index faaa6778f..5d2c9deb0 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -1,17 +1,7 @@
-import {
- AggregatedTranscript,
- DkgPublicKey,
- DkgPublicParameters,
- EthereumAddress,
- FerveoPublicKey,
- Transcript,
- Validator,
- ValidatorMessage,
-} from '@nucypher/nucypher-core';
+import { DkgPublicKey, DkgPublicParameters } from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
-import { DkgCoordinatorAgent } from './agents/coordinator';
-import { bytesEquals, fromHexString } from './utils';
+import { bytesEquals } from './utils';
// TOOD: Move to nucypher-core
export enum FerveoVariant {
@@ -70,25 +60,31 @@ export class DkgClient {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_ritualParams: unknown
): Promise {
+ // TODO: Remove this check after implementing this method
+ if (!this.provider._isProvider) {
+ throw new Error('Invalid provider');
+ }
// TODO: Create a new DKG ritual here
throw new Error('Not implemented');
}
- public async verifyRitual(ritualId: number): Promise {
- const ritual = await DkgCoordinatorAgent.getRitual(this.provider, ritualId);
- const participants = await DkgCoordinatorAgent.getParticipants(
- this.provider,
- ritualId
- );
-
- const validatorMessages = participants.map((p) => {
- const validatorAddress = EthereumAddress.fromString(p.node);
- const publicKey = FerveoPublicKey.fromBytes(fromHexString(p.publicKey));
- const validator = new Validator(validatorAddress, publicKey);
- const transcript = Transcript.fromBytes(fromHexString(p.transcript));
- return new ValidatorMessage(validator, transcript);
- });
- const aggregate = new AggregatedTranscript(validatorMessages);
-
- return aggregate.verify(ritual.dkgSize, validatorMessages);
- }
+ // TODO: Without Validator public key in Coordinator, we cannot verify the
+ // transcript. We need to add it to the Coordinator.
+ // public async verifyRitual(ritualId: number): Promise {
+ // const ritual = await DkgCoordinatorAgent.getRitual(this.provider, ritualId);
+ // const participants = await DkgCoordinatorAgent.getParticipants(
+ // this.provider,
+ // ritualId
+ // );
+ //
+ // const validatorMessages = participants.map((p) => {
+ // const validatorAddress = EthereumAddress.fromString(p.provider);
+ // const publicKey = FerveoPublicKey.fromBytes(fromHexString(p.???));
+ // const validator = new Validator(validatorAddress, publicKey);
+ // const transcript = Transcript.fromBytes(fromHexString(p.transcript));
+ // return new ValidatorMessage(validator, transcript);
+ // });
+ // const aggregate = new AggregatedTranscript(validatorMessages);
+ //
+ // return aggregate.verify(ritual.dkgSize, validatorMessages);
+ // }
}
diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts
index 5f2729fb7..951d84d34 100644
--- a/src/sdk/strategy/cbd-strategy.ts
+++ b/src/sdk/strategy/cbd-strategy.ts
@@ -49,7 +49,6 @@ export class CbdStrategy {
const decrypter = new CbdTDecDecrypter(
this.cohort.configuration.porterUri,
- this.cohort.ursulaAddresses,
this.cohort.configuration.threshold
);
@@ -128,7 +127,6 @@ export class DeployedCbdStrategy {
);
const decrypter = new CbdTDecDecrypter(
cohort.configuration.porterUri,
- cohort.ursulaAddresses,
cohort.configuration.threshold
);
return new DeployedCbdStrategy(
diff --git a/test/integration/dkg-client.test.ts b/test/integration/dkg-client.test.ts
index f14273979..aeaa282f9 100644
--- a/test/integration/dkg-client.test.ts
+++ b/test/integration/dkg-client.test.ts
@@ -1,7 +1,6 @@
import { SecretKey } from '@nucypher/nucypher-core';
import { DkgCoordinatorAgent } from '../../src/agents/coordinator';
-import { DkgClient } from '../../src/dkg';
import {
fakeCoordinatorRitual,
fakeDkgParticipants,
@@ -12,7 +11,7 @@ const ritualId = 1;
jest.mock('../../src/agents/coordinator', () => ({
DkgCoordinatorAgent: {
getRitual: () => Promise.resolve(fakeCoordinatorRitual(ritualId)),
- getParticipants: () => Promise.resolve(fakeDkgParticipants()),
+ getParticipants: () => Promise.resolve(fakeDkgParticipants(ritualId)),
},
}));
@@ -39,12 +38,13 @@ describe('DkgCoordinatorAgent', () => {
});
});
-describe('DkgClient', () => {
- it('verifies the dkg ritual', async () => {
- const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
-
- const dkgClient = new DkgClient(provider);
- const isValid = await dkgClient.verifyRitual(ritualId);
- expect(isValid).toBeTruthy();
- });
-});
+// TODO: Fix this test after the DkgClient.verifyRitual() method is implemented
+// describe('DkgClient', () => {
+// it('verifies the dkg ritual', async () => {
+// const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
+//
+// const dkgClient = new DkgClient(provider);
+// const isValid = await dkgClient.verifyRitual(ritualId);
+// expect(isValid).toBeTruthy();
+// });
+// });
diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts
index 9cf5621e0..12ad90bfd 100644
--- a/test/unit/cbd-strategy.test.ts
+++ b/test/unit/cbd-strategy.test.ts
@@ -16,6 +16,7 @@ import {
fakeWeb3Provider,
makeCohort,
mockCbdDecrypt,
+ mockGetParticipants,
mockGetUrsulas,
mockInitializeRitual,
} from '../utils';
@@ -36,11 +37,11 @@ const ownsNFT = new ERC721Ownership({
chain: 5,
});
const conditionSet = new ConditionSet([ownsNFT]);
-const mockedUrsulas = fakeUrsulas().slice(0, 3);
+const ursulas = fakeUrsulas().slice(0, 3);
const variant = FerveoVariant.Precomputed;
const makeCbdStrategy = async () => {
- const cohort = await makeCohort(mockedUrsulas);
+ const cohort = await makeCohort(ursulas);
const strategy = CbdStrategy.create(cohort, conditionSet);
expect(strategy.cohort).toEqual(cohort);
return strategy;
@@ -52,7 +53,7 @@ async function makeDeployedCbdStrategy() {
const mockedDkg = fakeDkgFlow(variant, 0);
const mockedDkgRitual = fakeDkgRitual(mockedDkg);
const web3Provider = fakeWeb3Provider(aliceSecretKey.toBEBytes());
- const getUrsulasSpy = mockGetUrsulas(mockedUrsulas);
+ const getUrsulasSpy = mockGetUrsulas(ursulas);
const initializeRitualSpy = mockInitializeRitual(mockedDkgRitual);
const deployedStrategy = await strategy.deploy(web3Provider);
@@ -114,11 +115,12 @@ describe('CbdDeployedStrategy', () => {
aad,
ciphertext,
});
- const getUrsulasSpy2 = mockGetUrsulas(mockedUrsulas);
+ const getUrsulasSpy2 = mockGetUrsulas(ursulas);
+ const getParticipantsSpy = mockGetParticipants(mockedDkg.ritualId, variant);
const decryptSpy = mockCbdDecrypt(
- mockedDkg.tau,
+ mockedDkg.ritualId,
decryptionShares,
- mockedUrsulas.map((u) => u.checksumAddress)
+ ursulas.map((u) => u.checksumAddress)
);
const decryptedMessage =
@@ -131,6 +133,7 @@ describe('CbdDeployedStrategy', () => {
aad
);
expect(getUrsulasSpy2).toHaveBeenCalled();
+ expect(getParticipantsSpy).toHaveBeenCalled();
expect(decryptSpy).toHaveBeenCalled();
expect(decryptedMessage[0]).toEqual(toBytes(message));
});
diff --git a/test/utils.ts b/test/utils.ts
index 995835c4c..146584e72 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -36,7 +36,7 @@ import { ethers, providers, Wallet } from 'ethers';
import { keccak256 } from 'ethers/lib/utils';
import { Alice, Bob, Cohort, Configuration, RemoteBob } from '../src';
-import { DkgParticipant } from '../src/agents/coordinator';
+import { DkgCoordinatorAgent, DkgParticipant } from '../src/agents/coordinator';
import {
CbdDecryptResult,
GetUrsulasResult,
@@ -103,41 +103,16 @@ export const fakeWeb3Provider = (
} as unknown as ethers.providers.Web3Provider;
};
-export const fakeUrsulas = (): readonly Ursula[] => {
- return [
- {
- encryptingKey: SecretKey.random().publicKey(),
- checksumAddress: '0x5cF1703A1c99A4b42Eb056535840e93118177232',
- uri: 'https://example.a.com:9151',
- },
- {
- encryptingKey: SecretKey.random().publicKey(),
- checksumAddress: '0x7fff551249D223f723557a96a0e1a469C79cC934',
- uri: 'https://example.b.com:9151',
- },
- {
- encryptingKey: SecretKey.random().publicKey(),
- checksumAddress: '0x9C7C824239D3159327024459Ad69bB215859Bd25',
- uri: 'https://example.c.com:9151',
- },
- {
- encryptingKey: SecretKey.random().publicKey(),
- checksumAddress: '0x9919C9f5CbBAA42CB3bEA153E14E16F85fEA5b5D',
- uri: 'https://example.d.com:9151',
- },
- {
- encryptingKey: SecretKey.random().publicKey(),
- checksumAddress: '0xfBeb3368735B3F0A65d1F1E02bf1d188bb5F5BE6',
- uri: 'https://example.e.com:9151',
- },
- ].map(({ encryptingKey, checksumAddress, uri }) => {
- return {
- checksumAddress: checksumAddress.toLowerCase(),
- encryptingKey,
- uri,
- };
- });
-};
+const genChecksumAddress = (i: number) =>
+ '0x' + '0'.repeat(40 - i.toString(16).length) + i.toString(16);
+const genEthAddr = (i: number) =>
+ EthereumAddress.fromString(genChecksumAddress(i));
+export const fakeUrsulas = (): readonly Ursula[] =>
+ [0, 1, 2, 3, 4].map((i: number) => ({
+ encryptingKey: SecretKey.random().publicKey(),
+ checksumAddress: genChecksumAddress(i).toLowerCase(),
+ uri: 'https://example.a.com:9151',
+ }));
export const mockGetUrsulas = (ursulas: readonly Ursula[]) => {
const fakePorterUrsulas = (
@@ -237,7 +212,7 @@ export const mockDetectEthereumProvider = () => {
export const fakeDkgFlow = (
variant: FerveoVariant | FerveoVariant.Precomputed,
- tau: number,
+ ritualId: number,
sharesNum = 4,
threshold = 3
) => {
@@ -247,12 +222,6 @@ export const fakeDkgFlow = (
) {
throw new Error(`Invalid variant: ${variant}`);
}
-
- const genEthAddr = (i: number) => {
- const ethAddr =
- '0x' + '0'.repeat(40 - i.toString(16).length) + i.toString(16);
- return EthereumAddress.fromString(ethAddr);
- };
const validatorKeypairs: Keypair[] = [];
const validators: Validator[] = [];
for (let i = 0; i < sharesNum; i++) {
@@ -267,7 +236,7 @@ export const fakeDkgFlow = (
const messages: ValidatorMessage[] = [];
const transcripts: Transcript[] = [];
validators.forEach((sender) => {
- const dkg = new Dkg(tau, sharesNum, threshold, validators, sender);
+ const dkg = new Dkg(ritualId, sharesNum, threshold, validators, sender);
const transcript = dkg.generateTranscript();
transcripts.push(transcript);
const message = new ValidatorMessage(sender, transcript);
@@ -276,7 +245,13 @@ export const fakeDkgFlow = (
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
- const dkg = new Dkg(tau, sharesNum, threshold, validators, validators[0]);
+ const dkg = new Dkg(
+ ritualId,
+ sharesNum,
+ threshold,
+ validators,
+ validators[0]
+ );
// Let's say that we've only received `threshold` transcripts
const receivedMessages = messages.slice(0, threshold);
@@ -288,7 +263,7 @@ export const fakeDkgFlow = (
const clientAggregate = new AggregatedTranscript(receivedMessages);
expect(clientAggregate.verify(sharesNum, receivedMessages)).toBeTruthy();
return {
- tau,
+ ritualId,
sharesNum,
threshold,
validatorKeypairs,
@@ -303,7 +278,7 @@ export const fakeDkgFlow = (
interface FakeDkgRitualFlow {
validators: Validator[];
validatorKeypairs: Keypair[];
- tau: number;
+ ritualId: number;
sharesNum: number;
threshold: number;
receivedMessages: ValidatorMessage[];
@@ -317,7 +292,7 @@ interface FakeDkgRitualFlow {
export const fakeTDecFlow = ({
validators,
validatorKeypairs,
- tau,
+ ritualId,
sharesNum,
threshold,
receivedMessages,
@@ -333,7 +308,7 @@ export const fakeTDecFlow = ({
| DecryptionShareSimple
)[] = [];
zip(validators, validatorKeypairs).forEach(([validator, keypair]) => {
- const dkg = new Dkg(tau, sharesNum, threshold, validators, validator);
+ const dkg = new Dkg(ritualId, sharesNum, threshold, validators, validator);
const aggregate = dkg.aggregateTranscript(receivedMessages);
const isValid = aggregate.verify(sharesNum, receivedMessages);
if (!isValid) {
@@ -444,21 +419,39 @@ export const fakeCoordinatorRitual = (
};
};
-export const fakeDkgParticipants = (): DkgParticipant[] => {
- const ritual = fakeDkgTDecFlowE2e(FerveoVariant.Precomputed);
- return zip(
- zip(ritual.validators, ritual.transcripts),
- ritual.validatorKeypairs
- ).map(([[v, t], k]) => {
+export const fakeDkgParticipants = (
+ ritualId: number,
+ variant = FerveoVariant.Precomputed
+): DkgParticipant[] => {
+ const ritual = fakeDkgTDecFlowE2e(variant);
+ const secretFactory = SessionSecretFactory.random();
+
+ return zip(ritual.validators, ritual.transcripts).map(([v, t]) => {
+ const label = toBytes(`${ritualId}`);
+ const decryptionRequestStaticKey = secretFactory.makeKey(label).publicKey();
return {
- node: v.address.toString(),
+ provider: v.address.toString(),
aggregated: true, // Assuming all validators already contributed to the aggregate
transcript: toHexString(t.toBytes()),
- publicKey: toHexString(k.publicKey.toBytes()),
- };
+ decryptionRequestStaticKey: toHexString(
+ decryptionRequestStaticKey.toBytes()
+ ),
+ } as DkgParticipant;
});
};
+export const mockGetParticipants = (
+ ritualId: number,
+ variant = FerveoVariant.Precomputed
+) => {
+ const participants = fakeDkgParticipants(ritualId, variant);
+ return jest
+ .spyOn(DkgCoordinatorAgent, 'getParticipants')
+ .mockImplementation(() => {
+ return Promise.resolve(participants);
+ });
+};
+
export const mockCbdDecrypt = (
ritualId: number,
decryptionShares: (DecryptionSharePrecomputed | DecryptionShareSimple)[],
From f9c43d0f75cae8eb28fca3e8c430acadc154c2fe Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 9 Jun 2023 17:39:17 +0200
Subject: [PATCH 4/6] properly establish session keys
---
src/characters/cbd-recipient.ts | 83 +++++++++++++++--------------
src/dkg.ts | 32 ++++++++++-
test/integration/dkg-client.test.ts | 20 ++++---
test/unit/cbd-strategy.test.ts | 20 +++++--
test/utils.ts | 80 ++++++++++++++++-----------
5 files changed, 148 insertions(+), 87 deletions(-)
diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts
index 125b39f6b..7cafa258a 100644
--- a/src/characters/cbd-recipient.ts
+++ b/src/characters/cbd-recipient.ts
@@ -1,16 +1,14 @@
import {
Ciphertext,
- combineDecryptionSharesPrecomputed,
- combineDecryptionSharesSimple,
Context,
DecryptionSharePrecomputed,
DecryptionShareSimple,
decryptWithSharedSecret,
EncryptedThresholdDecryptionRequest,
EncryptedThresholdDecryptionResponse,
- SessionSecretFactory,
SessionSharedSecret,
SessionStaticKey,
+ SessionStaticSecret,
SharedSecret,
ThresholdDecryptionRequest,
} from '@nucypher/nucypher-core';
@@ -18,8 +16,8 @@ import { ethers } from 'ethers';
import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator';
import { ConditionSet } from '../conditions';
-import { DkgRitual, FerveoVariant } from '../dkg';
-import { fromHexString, fromJSON, toBytes, toJSON } from '../utils';
+import { combineDecryptionSharesMap, DkgRitual, variantMap } from '../dkg';
+import { fromHexString, fromJSON, toJSON } from '../utils';
import { Porter } from './porter';
@@ -53,16 +51,10 @@ 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[]
- );
+ const combineDecryptionSharesFn = combineDecryptionSharesMap[variant];
+ if (combineDecryptionSharesFn) {
+ sharedSecret = combineDecryptionSharesFn(decryptionShares);
} else {
throw new Error(`Unknown variant ${variant}`);
}
@@ -83,13 +75,11 @@ export class CbdTDecDecrypter {
variant: number,
ciphertext: Ciphertext
): Promise {
- const contextStr = await conditionSet.buildContext(provider).toJson();
-
const dkgParticipants = await DkgCoordinatorAgent.getParticipants(
provider,
ritualId
);
-
+ const contextStr = await conditionSet.buildContext(provider).toJson();
const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests(
ritualId,
variant,
@@ -99,44 +89,50 @@ export class CbdTDecDecrypter {
dkgParticipants
);
- const { encryptedResponses } = await this.porter.cbdDecrypt(
+ 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
+ variant,
+ ritualId
);
}
private makeDecryptionShares(
encryptedResponses: Record,
sessionSharedSecret: Record,
- variant: number
+ variant: number,
+ expectedRitualId: number
) {
const decryptedResponses = Object.entries(encryptedResponses).map(
([ursula, encryptedResponse]) =>
encryptedResponse.decrypt(sessionSharedSecret[ursula])
);
- const variants = decryptedResponses.map((resp) => resp.ritualId);
- if (variants.some((v) => v !== variant)) {
- throw new Error('Decryption shares are not of the same variant');
+ 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(
- (resp) => resp.decryptionShare
+ ({ decryptionShare }) => decryptionShare
);
- // TODO: Replace with a factory method
- if (variant === FerveoVariant.Simple) {
- return decryptionShares.map((share) =>
- DecryptionShareSimple.fromBytes(share)
- );
- } else if (variant === FerveoVariant.Precomputed) {
+
+ const DecryptionShareType = variantMap[variant];
+ if (DecryptionShareType) {
return decryptionShares.map((share) =>
- DecryptionSharePrecomputed.fromBytes(share)
+ DecryptionShareType.fromBytes(share)
);
} else {
throw new Error(`Unknown variant ${variant}`);
@@ -161,22 +157,22 @@ export class CbdTDecDecrypter {
conditionSet.toWASMConditions(),
new Context(contextStr)
);
- const secretFactory = SessionSecretFactory.random();
- const label = toBytes(`${ritualId}`);
- const requesterSecretKey = secretFactory.makeKey(label);
+ const ephemeralSessionKey = this.makeSessionKey();
+
+ // Compute shared secrets for each participant
const sharedSecrets: Record =
Object.fromEntries(
- dkgParticipants.map((participant) => {
+ dkgParticipants.map(({ provider, decryptionRequestStaticKey }) => {
const decKey = SessionStaticKey.fromBytes(
- fromHexString(participant.decryptionRequestStaticKey)
+ fromHexString(decryptionRequestStaticKey)
);
- const sessionSharedSecret =
- requesterSecretKey.deriveSharedSecret(decKey);
- return [participant.provider, sessionSharedSecret];
+ const sharedSecret = ephemeralSessionKey.deriveSharedSecret(decKey);
+ return [provider, sharedSecret];
})
);
+ // Create encrypted requests for each participant
const encryptedRequests: Record<
string,
EncryptedThresholdDecryptionRequest
@@ -184,7 +180,7 @@ export class CbdTDecDecrypter {
Object.entries(sharedSecrets).map(([provider, sessionSharedSecret]) => {
const encryptedRequest = decryptionRequest.encrypt(
sessionSharedSecret,
- requesterSecretKey.publicKey()
+ ephemeralSessionKey.publicKey()
);
return [provider, encryptedRequest];
})
@@ -193,6 +189,11 @@ export class CbdTDecDecrypter {
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(),
diff --git a/src/dkg.ts b/src/dkg.ts
index 5d2c9deb0..6d0314437 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -1,14 +1,42 @@
-import { DkgPublicKey, DkgPublicParameters } from '@nucypher/nucypher-core';
+import {
+ combineDecryptionSharesPrecomputed,
+ combineDecryptionSharesSimple,
+ DecryptionSharePrecomputed,
+ DecryptionShareSimple,
+ DkgPublicKey,
+ DkgPublicParameters,
+ SharedSecret,
+} from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import { bytesEquals } from './utils';
-// TOOD: Move to nucypher-core
+// TODO: Expose from @nucypher/nucypher-core
export enum FerveoVariant {
Simple = 0,
Precomputed = 1,
}
+// TODO: Replace with a factory method
+export const variantMap: {
+ [key: number]:
+ | typeof DecryptionShareSimple
+ | typeof DecryptionSharePrecomputed;
+} = {
+ [FerveoVariant.Simple]: DecryptionShareSimple,
+ [FerveoVariant.Precomputed]: DecryptionSharePrecomputed,
+};
+
+// TODO: Replace with a factory method
+export const combineDecryptionSharesMap: {
+ [key: number]: (
+ shares: DecryptionShareSimple[] | DecryptionSharePrecomputed[]
+ ) => SharedSecret;
+} = {
+ [FerveoVariant.Simple]: combineDecryptionSharesSimple,
+ [FerveoVariant.Precomputed]: combineDecryptionSharesPrecomputed,
+};
+
export interface DkgRitualJSON {
id: number;
dkgPublicKey: Uint8Array;
diff --git a/test/integration/dkg-client.test.ts b/test/integration/dkg-client.test.ts
index aeaa282f9..acb8337b6 100644
--- a/test/integration/dkg-client.test.ts
+++ b/test/integration/dkg-client.test.ts
@@ -4,14 +4,15 @@ import { DkgCoordinatorAgent } from '../../src/agents/coordinator';
import {
fakeCoordinatorRitual,
fakeDkgParticipants,
+ fakeRitualId,
fakeWeb3Provider,
+ mockGetParticipants,
} from '../utils';
-const ritualId = 1;
jest.mock('../../src/agents/coordinator', () => ({
DkgCoordinatorAgent: {
- getRitual: () => Promise.resolve(fakeCoordinatorRitual(ritualId)),
- getParticipants: () => Promise.resolve(fakeDkgParticipants(ritualId)),
+ getRitual: () => Promise.resolve(fakeCoordinatorRitual(fakeRitualId)),
+ getParticipants: () => Promise.resolve(fakeDkgParticipants(fakeRitualId)),
},
}));
@@ -22,18 +23,21 @@ describe('DkgCoordinatorAgent', () => {
it('fetches transcripts from the coordinator', async () => {
const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
- const ritual = await DkgCoordinatorAgent.getRitual(provider, ritualId);
-
+ const ritual = await DkgCoordinatorAgent.getRitual(provider, fakeRitualId);
expect(ritual).toBeDefined();
});
it('fetches participants from the coordinator', async () => {
const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
+ const fakeParticipants = fakeDkgParticipants(fakeRitualId);
+ const getParticipantsSpy = mockGetParticipants(
+ fakeParticipants.participants
+ );
const participants = await DkgCoordinatorAgent.getParticipants(
provider,
- ritualId
+ fakeRitualId
);
-
+ expect(getParticipantsSpy).toHaveBeenCalled();
expect(participants.length).toBeGreaterThan(0);
});
});
@@ -44,7 +48,7 @@ describe('DkgCoordinatorAgent', () => {
// const provider = fakeWeb3Provider(SecretKey.random().toBEBytes());
//
// const dkgClient = new DkgClient(provider);
-// const isValid = await dkgClient.verifyRitual(ritualId);
+// const isValid = await dkgClient.verifyRitual(fakeRitualId);
// expect(isValid).toBeTruthy();
// });
// });
diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts
index 12ad90bfd..9774dcd43 100644
--- a/test/unit/cbd-strategy.test.ts
+++ b/test/unit/cbd-strategy.test.ts
@@ -1,4 +1,4 @@
-import { SecretKey } from '@nucypher/nucypher-core';
+import { SecretKey, SessionStaticSecret } from '@nucypher/nucypher-core';
import { conditions } from '../../src';
import { CbdTDecDecrypter } from '../../src/characters/cbd-recipient';
@@ -10,6 +10,7 @@ import {
import { toBytes } from '../../src/utils';
import {
fakeDkgFlow,
+ fakeDkgParticipants,
fakeDkgRitual,
fakeTDecFlow,
fakeUrsulas,
@@ -19,6 +20,7 @@ import {
mockGetParticipants,
mockGetUrsulas,
mockInitializeRitual,
+ mockRandomSessionStaticSecret,
} from '../utils';
import { aliceSecretKeyBytes } from './testVariables';
@@ -115,13 +117,20 @@ describe('CbdDeployedStrategy', () => {
aad,
ciphertext,
});
- const getUrsulasSpy2 = mockGetUrsulas(ursulas);
- const getParticipantsSpy = mockGetParticipants(mockedDkg.ritualId, variant);
+ const { participantSecrets, participants } = fakeDkgParticipants(
+ mockedDkg.ritualId,
+ variant
+ );
+ const requesterSessionKey = SessionStaticSecret.random();
const decryptSpy = mockCbdDecrypt(
mockedDkg.ritualId,
decryptionShares,
- ursulas.map((u) => u.checksumAddress)
+ participantSecrets,
+ requesterSessionKey.publicKey()
);
+ const getParticipantsSpy = mockGetParticipants(participants);
+ const getUrsulasSpy = mockGetUrsulas(ursulas);
+ const sessionKeySpy = mockRandomSessionStaticSecret(requesterSessionKey);
const decryptedMessage =
await deployedStrategy.decrypter.retrieveAndDecrypt(
@@ -132,8 +141,9 @@ describe('CbdDeployedStrategy', () => {
ciphertext,
aad
);
- expect(getUrsulasSpy2).toHaveBeenCalled();
+ expect(getUrsulasSpy).toHaveBeenCalled();
expect(getParticipantsSpy).toHaveBeenCalled();
+ expect(sessionKeySpy).toHaveBeenCalled();
expect(decryptSpy).toHaveBeenCalled();
expect(decryptedMessage[0]).toEqual(toBytes(message));
});
diff --git a/test/utils.ts b/test/utils.ts
index 146584e72..0d78c2102 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -12,6 +12,8 @@ import {
reencrypt,
SecretKey,
SessionSecretFactory,
+ SessionStaticKey,
+ SessionStaticSecret,
ThresholdDecryptionResponse,
VerifiedCapsuleFrag,
VerifiedKeyFrag,
@@ -37,6 +39,7 @@ import { keccak256 } from 'ethers/lib/utils';
import { Alice, Bob, Cohort, Configuration, RemoteBob } from '../src';
import { DkgCoordinatorAgent, DkgParticipant } from '../src/agents/coordinator';
+import { CbdTDecDecrypter } from '../src/characters/cbd-recipient';
import {
CbdDecryptResult,
GetUrsulasResult,
@@ -422,29 +425,36 @@ export const fakeCoordinatorRitual = (
export const fakeDkgParticipants = (
ritualId: number,
variant = FerveoVariant.Precomputed
-): DkgParticipant[] => {
+): {
+ participants: DkgParticipant[];
+ participantSecrets: Record;
+} => {
const ritual = fakeDkgTDecFlowE2e(variant);
- const secretFactory = SessionSecretFactory.random();
-
- return zip(ritual.validators, ritual.transcripts).map(([v, t]) => {
- const label = toBytes(`${ritualId}`);
- const decryptionRequestStaticKey = secretFactory.makeKey(label).publicKey();
+ const label = toBytes(`${ritualId}`);
+
+ const participantSecrets: Record =
+ Object.fromEntries(
+ ritual.validators.map(({ address }) => {
+ const participantSecret = SessionSecretFactory.random().makeKey(label);
+ return [address.toString(), participantSecret];
+ })
+ );
+
+ const participants: DkgParticipant[] = zip(
+ Object.entries(participantSecrets),
+ ritual.transcripts
+ ).map(([[address, secret], transcript]) => {
return {
- provider: v.address.toString(),
+ provider: address,
aggregated: true, // Assuming all validators already contributed to the aggregate
- transcript: toHexString(t.toBytes()),
- decryptionRequestStaticKey: toHexString(
- decryptionRequestStaticKey.toBytes()
- ),
+ transcript: toHexString(transcript.toBytes()),
+ decryptionRequestStaticKey: toHexString(secret.publicKey().toBytes()),
} as DkgParticipant;
});
+ return { participantSecrets, participants };
};
-export const mockGetParticipants = (
- ritualId: number,
- variant = FerveoVariant.Precomputed
-) => {
- const participants = fakeDkgParticipants(ritualId, variant);
+export const mockGetParticipants = (participants: DkgParticipant[]) => {
return jest
.spyOn(DkgCoordinatorAgent, 'getParticipants')
.mockImplementation(() => {
@@ -455,26 +465,22 @@ export const mockGetParticipants = (
export const mockCbdDecrypt = (
ritualId: number,
decryptionShares: (DecryptionSharePrecomputed | DecryptionShareSimple)[],
- ursulas: ChecksumAddress[],
+ participantSecrets: Record,
+ requesterPk: SessionStaticKey,
errors: Record = {}
) => {
const encryptedResponses: Record<
string,
EncryptedThresholdDecryptionResponse
> = Object.fromEntries(
- zip(decryptionShares, ursulas).map(([share, ursula]) => {
- const secretFactory = SessionSecretFactory.random();
- const label = toBytes(`${ritualId}`);
- const ursulaPublicKey = secretFactory.makeKey(label).publicKey();
- const requesterSecretKey = secretFactory.makeKey(label);
- const sessionSharedSecret =
- requesterSecretKey.deriveSharedSecret(ursulaPublicKey);
-
- const resp = new ThresholdDecryptionResponse(ritualId, share.toBytes());
- const encryptedResp = resp.encrypt(sessionSharedSecret);
-
- return [ursula, encryptedResp];
- })
+ zip(decryptionShares, Object.entries(participantSecrets)).map(
+ ([share, [address, secret]]) => {
+ const resp = new ThresholdDecryptionResponse(ritualId, share.toBytes());
+ const sessionSecret = secret.deriveSharedSecret(requesterPk);
+ const encryptedResp = resp.encrypt(sessionSecret);
+ return [address, encryptedResp];
+ }
+ )
);
const result: CbdDecryptResult = {
@@ -486,8 +492,20 @@ export const mockCbdDecrypt = (
});
};
+export const mockRandomSessionStaticSecret = (secret: SessionStaticSecret) => {
+ return jest
+ .spyOn(CbdTDecDecrypter.prototype as any, 'makeSessionKey')
+ .mockImplementation(() => secret);
+};
+
+export const fakeRitualId = 0;
+
export const fakeDkgRitual = (ritual: { dkg: Dkg }) => {
- return new DkgRitual(1, ritual.dkg.publicKey(), ritual.dkg.publicParams());
+ return new DkgRitual(
+ fakeRitualId,
+ ritual.dkg.publicKey(),
+ ritual.dkg.publicParams()
+ );
};
export const mockInitializeRitual = (fakeRitual: unknown) => {
From 03eb03efe5a37e15e55741bc12b88819e863db1b Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Fri, 9 Jun 2023 17:57:21 +0200
Subject: [PATCH 5/6] self review
---
src/characters/cbd-recipient.ts | 33 ++++++++++-------------
src/dkg.ts | 46 +++++++++++++++++++-------------
src/sdk/strategy/cbd-strategy.ts | 12 ++++-----
src/sdk/strategy/pre-strategy.ts | 4 +--
4 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts
index 7cafa258a..d3dda39a3 100644
--- a/src/characters/cbd-recipient.ts
+++ b/src/characters/cbd-recipient.ts
@@ -9,14 +9,17 @@ import {
SessionSharedSecret,
SessionStaticKey,
SessionStaticSecret,
- SharedSecret,
ThresholdDecryptionRequest,
} from '@nucypher/nucypher-core';
import { ethers } from 'ethers';
import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator';
import { ConditionSet } from '../conditions';
-import { combineDecryptionSharesMap, DkgRitual, variantMap } from '../dkg';
+import {
+ DkgRitual,
+ getCombineDecryptionSharesFunction,
+ getVariantClass,
+} from '../dkg';
import { fromHexString, fromJSON, toJSON } from '../utils';
import { Porter } from './porter';
@@ -29,12 +32,11 @@ export type CbdTDecDecrypterJSON = {
export class CbdTDecDecrypter {
private readonly porter: Porter;
- // private readonly verifyingKey: Keyring;
-
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,
@@ -51,13 +53,9 @@ export class CbdTDecDecrypter {
ciphertext
);
- let sharedSecret: SharedSecret;
- const combineDecryptionSharesFn = combineDecryptionSharesMap[variant];
- if (combineDecryptionSharesFn) {
- sharedSecret = combineDecryptionSharesFn(decryptionShares);
- } else {
- throw new Error(`Unknown variant ${variant}`);
- }
+ const combineDecryptionSharesFn =
+ getCombineDecryptionSharesFunction(variant);
+ const sharedSecret = combineDecryptionSharesFn(decryptionShares);
const plaintext = decryptWithSharedSecret(
ciphertext,
@@ -68,6 +66,7 @@ export class CbdTDecDecrypter {
return [plaintext];
}
+ // Retrieve decryption shares
public async retrieve(
provider: ethers.providers.Web3Provider,
conditionSet: ConditionSet,
@@ -129,14 +128,10 @@ export class CbdTDecDecrypter {
({ decryptionShare }) => decryptionShare
);
- const DecryptionShareType = variantMap[variant];
- if (DecryptionShareType) {
- return decryptionShares.map((share) =>
- DecryptionShareType.fromBytes(share)
- );
- } else {
- throw new Error(`Unknown variant ${variant}`);
- }
+ const DecryptionShareType = getVariantClass(variant);
+ return decryptionShares.map((share) =>
+ DecryptionShareType.fromBytes(share)
+ );
}
private makeDecryptionRequests(
diff --git a/src/dkg.ts b/src/dkg.ts
index 6d0314437..a2b805fca 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -17,25 +17,32 @@ export enum FerveoVariant {
Precomputed = 1,
}
-// TODO: Replace with a factory method
-export const variantMap: {
- [key: number]:
- | typeof DecryptionShareSimple
- | typeof DecryptionSharePrecomputed;
-} = {
- [FerveoVariant.Simple]: DecryptionShareSimple,
- [FerveoVariant.Precomputed]: DecryptionSharePrecomputed,
-};
-
-// TODO: Replace with a factory method
-export const combineDecryptionSharesMap: {
- [key: number]: (
- shares: DecryptionShareSimple[] | DecryptionSharePrecomputed[]
- ) => SharedSecret;
-} = {
- [FerveoVariant.Simple]: combineDecryptionSharesSimple,
- [FerveoVariant.Precomputed]: combineDecryptionSharesPrecomputed,
-};
+export function getVariantClass(
+ variant: FerveoVariant
+): typeof DecryptionShareSimple | typeof DecryptionSharePrecomputed {
+ switch (variant) {
+ case FerveoVariant.Simple:
+ return DecryptionShareSimple;
+ case FerveoVariant.Precomputed:
+ return DecryptionSharePrecomputed;
+ default:
+ throw new Error(`Invalid FerveoVariant: ${variant}`);
+ }
+}
+export function getCombineDecryptionSharesFunction(
+ variant: FerveoVariant
+): (
+ shares: DecryptionShareSimple[] | DecryptionSharePrecomputed[]
+) => SharedSecret {
+ switch (variant) {
+ case FerveoVariant.Simple:
+ return combineDecryptionSharesSimple;
+ case FerveoVariant.Precomputed:
+ return combineDecryptionSharesPrecomputed;
+ default:
+ throw new Error(`Invalid FerveoVariant: ${variant}`);
+ }
+}
export interface DkgRitualJSON {
id: number;
@@ -95,6 +102,7 @@ export class DkgClient {
// TODO: Create a new DKG ritual here
throw new Error('Not implemented');
}
+
// TODO: Without Validator public key in Coordinator, we cannot verify the
// transcript. We need to add it to the Coordinator.
// public async verifyRitual(ritualId: number): Promise {
diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts
index 951d84d34..fa4f5f072 100644
--- a/src/sdk/strategy/cbd-strategy.ts
+++ b/src/sdk/strategy/cbd-strategy.ts
@@ -87,18 +87,18 @@ export class CbdStrategy {
const conditionSetEquals =
this.conditionSet && other.conditionSet
? this.conditionSet.equals(other.conditionSet)
- : false;
+ : this.conditionSet === other.conditionSet;
return this.cohort.equals(other.cohort) && conditionSetEquals;
}
}
export class DeployedCbdStrategy {
constructor(
- public cohort: Cohort,
- public dkgRitual: DkgRitual,
- public encrypter: Enrico,
- public decrypter: CbdTDecDecrypter,
- public conditionSet?: ConditionSet
+ public readonly cohort: Cohort,
+ public readonly dkgRitual: DkgRitual,
+ public readonly encrypter: Enrico,
+ public readonly decrypter: CbdTDecDecrypter,
+ public readonly conditionSet?: ConditionSet
) {}
public static fromJSON(json: string) {
diff --git a/src/sdk/strategy/pre-strategy.ts b/src/sdk/strategy/pre-strategy.ts
index 2c0dce71c..88c79b91a 100644
--- a/src/sdk/strategy/pre-strategy.ts
+++ b/src/sdk/strategy/pre-strategy.ts
@@ -160,7 +160,7 @@ export class PreStrategy {
const conditionSetEquals =
this.conditionSet && other.conditionSet
? this.conditionSet.equals(other.conditionSet)
- : false;
+ : this.conditionSet === other.conditionSet;
return (
this.cohort.equals(other.cohort) &&
// TODO: Add equality to WASM bindings
@@ -274,7 +274,7 @@ export class DeployedPreStrategy {
const conditionSetEquals =
this.conditionSet && other.conditionSet
? this.conditionSet.equals(other.conditionSet)
- : false;
+ : this.conditionSet === other.conditionSet;
return (
this.label === other.label &&
this.cohort.equals(other.cohort) &&
From e23b0cbad59acd22ef0060263e312d907a7db866 Mon Sep 17 00:00:00 2001
From: piotr-roslaniec <39299780+piotr-roslaniec@users.noreply.github.com>
Date: Mon, 12 Jun 2023 21:55:56 +0200
Subject: [PATCH 6/6] Update comment
Co-authored-by: Derek Pierre
---
src/dkg.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/dkg.ts b/src/dkg.ts
index a2b805fca..c0bbd49e7 100644
--- a/src/dkg.ts
+++ b/src/dkg.ts
@@ -104,7 +104,7 @@ export class DkgClient {
}
// TODO: Without Validator public key in Coordinator, we cannot verify the
- // transcript. We need to add it to the Coordinator.
+ // transcript. We need to add it to the Coordinator (nucypher-contracts #77).
// public async verifyRitual(ritualId: number): Promise {
// const ritual = await DkgCoordinatorAgent.getRitual(this.provider, ritualId);
// const participants = await DkgCoordinatorAgent.getParticipants(