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(