diff --git a/src/characters/alice.ts b/src/characters/alice.ts index 417213b64..dfe06d02f 100644 --- a/src/characters/alice.ts +++ b/src/characters/alice.ts @@ -6,7 +6,6 @@ import { } from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; -import { Configuration } from '../config'; import { Keyring } from '../keyring'; import { BlockchainPolicy, @@ -14,21 +13,15 @@ import { EnactedPolicy, PreEnactedPolicy, } from '../policies/policy'; +import { PorterClient } from '../porter'; import { ChecksumAddress } from '../types'; import { RemoteBob } from './bob'; -import { Porter } from './porter'; export class Alice { - private readonly porter: Porter; private readonly keyring: Keyring; - private constructor( - config: Configuration, - secretKey: SecretKey, - public readonly web3Provider: ethers.providers.Web3Provider - ) { - this.porter = new Porter(config.porterUri); + private constructor(secretKey: SecretKey) { this.keyring = new Keyring(secretKey); } @@ -40,12 +33,8 @@ export class Alice { return this.keyring.signer; } - public static fromSecretKey( - config: Configuration, - secretKey: SecretKey, - web3Provider: ethers.providers.Web3Provider - ): Alice { - return new Alice(config, secretKey, web3Provider); + public static fromSecretKey(secretKey: SecretKey): Alice { + return new Alice(secretKey); } public getPolicyEncryptingKeyFromLabel(label: string): PublicKey { @@ -53,30 +42,36 @@ export class Alice { } public async grant( + web3Provider: ethers.providers.Web3Provider, + porterUri: string, policyParameters: BlockchainPolicyParameters, includeUrsulas?: readonly ChecksumAddress[], excludeUrsulas?: readonly ChecksumAddress[] ): Promise { - const ursulas = await this.porter.getUrsulas( + const porter = new PorterClient(porterUri); + const ursulas = await porter.getUrsulas( policyParameters.shares, excludeUrsulas, includeUrsulas ); - const policy = await this.createPolicy(policyParameters); - return await policy.enact(ursulas); + const policy = await this.createPolicy(web3Provider, policyParameters); + return await policy.enact(web3Provider, ursulas); } public async generatePreEnactedPolicy( + web3Provider: ethers.providers.Web3Provider, + porterUri: string, policyParameters: BlockchainPolicyParameters, includeUrsulas?: readonly ChecksumAddress[], excludeUrsulas?: readonly ChecksumAddress[] ): Promise { - const ursulas = await this.porter.getUrsulas( + const porter = new PorterClient(porterUri); + const ursulas = await porter.getUrsulas( policyParameters.shares, excludeUrsulas, includeUrsulas ); - const policy = await this.createPolicy(policyParameters); + const policy = await this.createPolicy(web3Provider, policyParameters); return await policy.generatePreEnactedPolicy(ursulas); } @@ -99,10 +94,11 @@ export class Alice { } private async createPolicy( + web3Provider: ethers.providers.Web3Provider, rawParameters: BlockchainPolicyParameters ): Promise { const { bob, label, threshold, shares, startDate, endDate } = - await this.validatePolicyParameters(rawParameters); + await this.validatePolicyParameters(web3Provider, rawParameters); const { delegatingKey, verifiedKFrags } = this.generateKFrags( bob, label, @@ -123,6 +119,7 @@ export class Alice { } private async validatePolicyParameters( + web3Provider: ethers.providers.Web3Provider, rawParams: BlockchainPolicyParameters ): Promise { const startDate = rawParams.startDate ?? new Date(); @@ -144,8 +141,8 @@ export class Alice { ); } - const blockNumber = await this.web3Provider.getBlockNumber(); - const block = await this.web3Provider.getBlock(blockNumber); + const blockNumber = await web3Provider.getBlockNumber(); + const block = await web3Provider.getBlock(blockNumber); const blockTime = new Date(block.timestamp * 1000); if (endDate < blockTime) { throw new Error( diff --git a/src/characters/bob.ts b/src/characters/bob.ts index 1ee8606af..1959c77da 100644 --- a/src/characters/bob.ts +++ b/src/characters/bob.ts @@ -6,14 +6,12 @@ import { Signer, } from '@nucypher/nucypher-core'; -import { Configuration } from '../config'; import { Keyring } from '../keyring'; import { PolicyMessageKit } from '../kits/message'; import { RetrievalResult } from '../kits/retrieval'; +import { PorterClient } from '../porter'; import { zip } from '../utils'; -import { Porter } from './porter'; - export class RemoteBob { private constructor( public readonly decryptingKey: PublicKey, @@ -37,11 +35,9 @@ export class RemoteBob { } export class Bob { - private readonly porter: Porter; private readonly keyring: Keyring; - constructor(config: Configuration, secretKey: SecretKey) { - this.porter = new Porter(config.porterUri); + constructor(secretKey: SecretKey) { this.keyring = new Keyring(secretKey); } @@ -57,11 +53,8 @@ export class Bob { return this.keyring.signer; } - public static fromSecretKey( - config: Configuration, - secretKey: SecretKey - ): Bob { - return new Bob(config, secretKey); + public static fromSecretKey(secretKey: SecretKey): Bob { + return new Bob(secretKey); } public decrypt(messageKit: MessageKit | PolicyMessageKit): Uint8Array { @@ -69,12 +62,14 @@ export class Bob { } public async retrieveAndDecrypt( + porterUri: string, policyEncryptingKey: PublicKey, publisherVerifyingKey: PublicKey, messageKits: readonly MessageKit[], encryptedTreasureMap: EncryptedTreasureMap ): Promise { const policyMessageKits = await this.retrieve( + porterUri, policyEncryptingKey, publisherVerifyingKey, messageKits, @@ -103,6 +98,7 @@ export class Bob { } public async retrieve( + porterUri: string, policyEncryptingKey: PublicKey, publisherVerifyingKey: PublicKey, messageKits: readonly MessageKit[], @@ -122,7 +118,8 @@ export class Bob { ); const retrievalKits = policyMessageKits.map((pk) => pk.asRetrievalKit()); - const retrieveCFragsResponses = await this.porter.retrieveCFrags( + const porter = new PorterClient(porterUri); + const retrieveCFragsResponses = await porter.retrieveCFrags( treasureMap, retrievalKits, publisherVerifyingKey, diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts index 48a21e4eb..04ddf181b 100644 --- a/src/characters/cbd-recipient.ts +++ b/src/characters/cbd-recipient.ts @@ -20,28 +20,27 @@ import { getCombineDecryptionSharesFunction, getVariantClass, } from '../dkg'; +import { PorterClient } from '../porter'; import { fromJSON, toJSON } from '../utils'; -import { Porter } from './porter'; - -export type CbdTDecDecrypterJSON = { +export type ThresholdDecrypterJSON = { porterUri: string; ritualId: number; threshold: number; }; -export class CbdTDecDecrypter { +export class ThresholdDecrypter { // private readonly verifyingKey: Keyring; private constructor( - private readonly porter: Porter, + private readonly porter: PorterClient, private readonly ritualId: number, private readonly threshold: number ) {} public static create(porterUri: string, dkgRitual: DkgRitual) { - return new CbdTDecDecrypter( - new Porter(porterUri), + return new ThresholdDecrypter( + new PorterClient(porterUri), dkgRitual.id, dkgRitual.dkgParams.threshold ); @@ -193,7 +192,7 @@ export class CbdTDecDecrypter { return SessionStaticSecret.random(); } - public toObj(): CbdTDecDecrypterJSON { + public toObj(): ThresholdDecrypterJSON { return { porterUri: this.porter.porterUrl.toString(), ritualId: this.ritualId, @@ -209,15 +208,19 @@ export class CbdTDecDecrypter { porterUri, ritualId, threshold, - }: CbdTDecDecrypterJSON) { - return new CbdTDecDecrypter(new Porter(porterUri), ritualId, threshold); + }: ThresholdDecrypterJSON) { + return new ThresholdDecrypter( + new PorterClient(porterUri), + ritualId, + threshold + ); } public static fromJSON(json: string) { - return CbdTDecDecrypter.fromObj(fromJSON(json)); + return ThresholdDecrypter.fromObj(fromJSON(json)); } - public equals(other: CbdTDecDecrypter): boolean { + public equals(other: ThresholdDecrypter): boolean { return ( this.porter.porterUrl.toString() === other.porter.porterUrl.toString() ); diff --git a/src/characters/pre-recipient.ts b/src/characters/pre-recipient.ts index 329d9c627..f3ae24f5a 100644 --- a/src/characters/pre-recipient.ts +++ b/src/characters/pre-recipient.ts @@ -12,11 +12,10 @@ import { ConditionContext, ConditionExpression } from '../conditions'; import { Keyring } from '../keyring'; import { PolicyMessageKit } from '../kits/message'; import { RetrievalResult } from '../kits/retrieval'; -import { base64ToU8Receiver, bytesEquals, toJSON, zip } from '../utils'; +import { PorterClient } from '../porter'; +import { base64ToU8Receiver, toJSON, zip } from '../utils'; -import { Porter } from './porter'; - -export type PreTDecDecrypterJSON = { +export type PreDecrypterJSON = { porterUri: string; policyEncryptingKeyBytes: Uint8Array; encryptedTreasureMapBytes: Uint8Array; @@ -24,11 +23,11 @@ export type PreTDecDecrypterJSON = { bobSecretKeyBytes: Uint8Array; }; -export class PreTDecDecrypter { +export class PreDecrypter { // private readonly verifyingKey: Keyring; constructor( - private readonly porter: Porter, + private readonly porter: PorterClient, private readonly keyring: Keyring, private readonly policyEncryptingKey: PublicKey, private readonly publisherVerifyingKey: PublicKey, @@ -41,9 +40,9 @@ export class PreTDecDecrypter { policyEncryptingKey: PublicKey, publisherVerifyingKey: PublicKey, encryptedTreasureMap: EncryptedTreasureMap - ): PreTDecDecrypter { - return new PreTDecDecrypter( - new Porter(porterUri), + ): PreDecrypter { + return new PreDecrypter( + new PorterClient(porterUri), new Keyring(secretKey), policyEncryptingKey, publisherVerifyingKey, @@ -149,7 +148,7 @@ export class PreTDecDecrypter { }); } - public toObj(): PreTDecDecrypterJSON { + public toObj(): PreDecrypterJSON { return { porterUri: this.porter.porterUrl.toString(), policyEncryptingKeyBytes: this.policyEncryptingKey.toCompressedBytes(), @@ -170,9 +169,9 @@ export class PreTDecDecrypter { encryptedTreasureMapBytes, publisherVerifyingKeyBytes, bobSecretKeyBytes, - }: PreTDecDecrypterJSON) { - return new PreTDecDecrypter( - new Porter(porterUri), + }: PreDecrypterJSON) { + return new PreDecrypter( + new PorterClient(porterUri), new Keyring(SecretKey.fromBEBytes(bobSecretKeyBytes)), PublicKey.fromCompressedBytes(policyEncryptingKeyBytes), PublicKey.fromCompressedBytes(publisherVerifyingKeyBytes), @@ -182,19 +181,15 @@ export class PreTDecDecrypter { public static fromJSON(json: string) { const config = JSON.parse(json, base64ToU8Receiver); - return PreTDecDecrypter.fromObj(config); + return PreDecrypter.fromObj(config); } - public equals(other: PreTDecDecrypter): boolean { - return ( - this.porter.porterUrl.toString() === other.porter.porterUrl.toString() && - this.policyEncryptingKey.equals(other.policyEncryptingKey) && - // TODO: Replace with `equals` after https://github.com/nucypher/nucypher-core/issues/56 is fixed - bytesEquals( - this.encryptedTreasureMap.toBytes(), - other.encryptedTreasureMap.toBytes() - ) && - this.publisherVerifyingKey.equals(other.publisherVerifyingKey) - ); + public equals(other: PreDecrypter): boolean { + return [ + this.porter.porterUrl.toString() === other.porter.porterUrl.toString(), + this.policyEncryptingKey.equals(other.policyEncryptingKey), + this.encryptedTreasureMap.equals(other.encryptedTreasureMap), + this.publisherVerifyingKey.equals(other.publisherVerifyingKey), + ].every(Boolean); } } diff --git a/src/conditions/condition-expr.ts b/src/conditions/condition-expr.ts index 5cc032c5e..61c446e53 100644 --- a/src/conditions/condition-expr.ts +++ b/src/conditions/condition-expr.ts @@ -95,9 +95,9 @@ export class ConditionExpression { } public equals(other: ConditionExpression): boolean { - return ( - this.version === other.version && - objectEquals(this.condition.toObj(), other.condition.toObj()) - ); + return [ + this.version === other.version, + objectEquals(this.condition.toObj(), other.condition.toObj()), + ].every(Boolean); } } diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index 1b59d0c4e..000000000 --- a/src/config.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ChainId } from './types'; - -export type Configuration = { - readonly porterUri: string; -}; - -const CONFIGS: { readonly [key in ChainId]: Configuration } = { - [ChainId.POLYGON]: { - porterUri: 'https://porter.nucypher.community', - }, - [ChainId.MUMBAI]: { - porterUri: 'https://porter-tapir.nucypher.community', - }, - [ChainId.GOERLI]: { - // TODO: Confirm this is correct - porterUri: 'https://porter-tapir.nucypher.community', - }, - [ChainId.MAINNET]: { - // TODO: Confirm this is correct - porterUri: 'https://porter.nucypher.io/', - }, -}; - -export const defaultConfiguration = (chainId: number): Configuration => { - if (!Object.values(ChainId).includes(chainId)) { - throw new Error(`No default configuration found for chainId: ${chainId}`); - } - return CONFIGS[chainId as ChainId]; -}; diff --git a/src/dkg.ts b/src/dkg.ts index f5def34ec..a728b7a59 100644 --- a/src/dkg.ts +++ b/src/dkg.ts @@ -11,7 +11,7 @@ import { ethers } from 'ethers'; import { DkgCoordinatorAgent, DkgRitualState } from './agents/coordinator'; import { ChecksumAddress } from './types'; -import { bytesEquals, fromHexString, objectEquals } from './utils'; +import { fromHexString, objectEquals } from './utils'; export function getVariantClass( variant: FerveoVariant @@ -83,13 +83,12 @@ export class DkgRitual { } public equals(other: DkgRitual): boolean { - return ( - this.id === other.id && - // TODO: Replace with `equals` after https://github.com/nucypher/nucypher-core/issues/56 is fixed - bytesEquals(this.dkgPublicKey.toBytes(), other.dkgPublicKey.toBytes()) && - objectEquals(this.dkgParams, other.dkgParams) && - this.state === other.state - ); + return [ + this.id === other.id, + this.dkgPublicKey.equals(other.dkgPublicKey), + objectEquals(this.dkgParams, other.dkgParams), + this.state === other.state, + ].every(Boolean); } } diff --git a/src/index.ts b/src/index.ts index 0b3e89136..0045b30bc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,8 @@ export { Alice } from './characters/alice'; export { Bob, RemoteBob } from './characters/bob'; export { Enrico } from './characters/enrico'; -export { PreTDecDecrypter } from './characters/pre-recipient'; -export { Porter } from './characters/porter'; +export { PreDecrypter } from './characters/pre-recipient'; +export { PorterClient } from './porter'; // Policies export type { @@ -15,9 +15,8 @@ export { PreEnactedPolicy } from './policies/policy'; // Keyring export { Keyring } from './keyring'; -// Configuration -export type { Configuration } from './config'; -export { defaultConfiguration } from './config'; +// Porter +export { getPorterUri } from './porter'; // Kits export { PolicyMessageKit } from './kits/message'; diff --git a/src/policies/policy.ts b/src/policies/policy.ts index b17d6b81f..00c62003a 100644 --- a/src/policies/policy.ts +++ b/src/policies/policy.ts @@ -6,11 +6,12 @@ import { TreasureMap, VerifiedKeyFrag, } from '@nucypher/nucypher-core'; +import { ethers } from 'ethers'; import { PreSubscriptionManagerAgent } from '../agents/subscription-manager'; import { Alice } from '../characters/alice'; import { RemoteBob } from '../characters/bob'; -import { Ursula } from '../characters/porter'; +import { Ursula } from '../porter'; import { toBytes, toEpoch, zip } from '../utils'; import { toCanonicalAddress } from '../web3'; @@ -28,15 +29,6 @@ export type EnactedPolicy = { type IPreEnactedPolicy = Omit; -export type EnactedPolicyJSON = Omit< - EnactedPolicy, - 'policyKey' | 'encryptedTreasureMap' | 'id' -> & { - policyKey: Uint8Array; - id: Uint8Array; - encryptedTreasureMap: Uint8Array; -}; - export class PreEnactedPolicy implements IPreEnactedPolicy { constructor( public readonly id: HRAC, @@ -49,26 +41,30 @@ export class PreEnactedPolicy implements IPreEnactedPolicy { public readonly endTimestamp: Date ) {} - public async enact(publisher: Alice): Promise { - const txHash = await this.publish(publisher); + public async enact( + web3Provider: ethers.providers.Web3Provider + ): Promise { + const txHash = await this.publish(web3Provider); return { ...this, txHash, }; } - private async publish(publisher: Alice): Promise { + private async publish( + web3Provider: ethers.providers.Web3Provider + ): Promise { const startTimestamp = toEpoch(this.startTimestamp); const endTimestamp = toEpoch(this.endTimestamp); - const ownerAddress = await publisher.web3Provider.getSigner().getAddress(); + const ownerAddress = await web3Provider.getSigner().getAddress(); const value = await PreSubscriptionManagerAgent.getPolicyCost( - publisher.web3Provider, + web3Provider, this.size, startTimestamp, endTimestamp ); const tx = await PreSubscriptionManagerAgent.createPolicy( - publisher.web3Provider, + web3Provider, value, this.id.toBytes(), this.size, @@ -110,9 +106,12 @@ export class BlockchainPolicy { ); } - public async enact(ursulas: readonly Ursula[]): Promise { + public async enact( + web3Provider: ethers.providers.Web3Provider, + ursulas: readonly Ursula[] + ): Promise { const preEnacted = await this.generatePreEnactedPolicy(ursulas); - return await preEnacted.enact(this.publisher); + return await preEnacted.enact(web3Provider); } public async generatePreEnactedPolicy( @@ -143,18 +142,18 @@ export class BlockchainPolicy { ursulas: readonly Ursula[], verifiedKFrags: readonly VerifiedKeyFrag[] ): TreasureMap { - const assigned_kfrags: [Address, [PublicKey, VerifiedKeyFrag]][] = []; + const assignedKFrags: [Address, [PublicKey, VerifiedKeyFrag]][] = []; zip(ursulas, verifiedKFrags).forEach(([ursula, kFrag]) => { const ursulaAddress = new Address( toCanonicalAddress(ursula.checksumAddress) ); - assigned_kfrags.push([ursulaAddress, [ursula.encryptingKey, kFrag]]); + assignedKFrags.push([ursulaAddress, [ursula.encryptingKey, kFrag]]); }); return new TreasureMap( this.publisher.signer, this.hrac, this.delegatingKey, - assigned_kfrags, + assignedKFrags, this.threshold ); } diff --git a/src/characters/porter.ts b/src/porter.ts similarity index 90% rename from src/characters/porter.ts rename to src/porter.ts index 2a3ef1d6a..f222f2e25 100644 --- a/src/characters/porter.ts +++ b/src/porter.ts @@ -9,8 +9,25 @@ import { import axios, { AxiosResponse } from 'axios'; import qs from 'qs'; -import { Base64EncodedBytes, ChecksumAddress, HexEncodedBytes } from '../types'; -import { fromBase64, fromHexString, toBase64, toHexString } from '../utils'; +import { Base64EncodedBytes, ChecksumAddress, HexEncodedBytes } from './types'; +import { fromBase64, fromHexString, toBase64, toHexString } from './utils'; + +type Network = 'mainnet' | 'tapir' | 'oryx' | 'lynx'; + +const PORTER_URIS: Record = { + mainnet: 'https://porter.nucypher.community', + tapir: 'https://porter-tapir.nucypher.community', + oryx: 'https://porter-oryx.nucypher.community', + lynx: 'https://porter-lynx.nucypher.community', +}; + +export const getPorterUri = (network: Network): string => { + const uri = PORTER_URIS[network]; + if (!uri) { + throw new Error(`No default Porter URI found for network: ${network}`); + } + return PORTER_URIS[network]; +}; // /get_ursulas @@ -96,7 +113,7 @@ export type CbdDecryptResult = { errors: Record; }; -export class Porter { +export class PorterClient { readonly porterUrl: URL; constructor(porterUri: string) { diff --git a/src/sdk/cohort.ts b/src/sdk/cohort.ts index 070027186..d377a6a4b 100644 --- a/src/sdk/cohort.ts +++ b/src/sdk/cohort.ts @@ -1,51 +1,34 @@ -import { Porter } from '../characters/porter'; +import { PorterClient } from '../porter'; import { ChecksumAddress } from '../types'; import { objectEquals } from '../utils'; -export type CohortConfiguration = { - readonly threshold: number; - readonly shares: number; - readonly porterUri: string; -}; - export type CohortJSON = { ursulaAddresses: ChecksumAddress[]; - threshold: number; - shares: number; porterUri: string; }; export class Cohort { private constructor( - public ursulaAddresses: ChecksumAddress[], - public readonly configuration: CohortConfiguration + public readonly ursulaAddresses: ChecksumAddress[], + public readonly porterUri: string ) {} public static async create( - configuration: CohortConfiguration, + porterUri: string, + numUrsulas: number, include: string[] = [], exclude: string[] = [] ) { - if (configuration.threshold > configuration.shares) { - throw new Error('Threshold cannot be greater than the number of shares'); - } - // TODO: Remove this limitation after `nucypher-core@0.11.0` deployment - const isMultipleOf2 = (n: number) => n % 2 === 0; - if (!isMultipleOf2(configuration.shares)) { - throw new Error('Number of shares must be a multiple of 2'); - } - - const porter = new Porter(configuration.porterUri); - const ursulas = await porter.getUrsulas( - configuration.shares, - exclude, - include.splice(0, configuration.shares) - ); + const porter = new PorterClient(porterUri); + const ursulas = await porter.getUrsulas(numUrsulas, exclude, include); const ursulaAddresses = ursulas.map((ursula) => ursula.checksumAddress); - return new Cohort(ursulaAddresses, configuration); + return new Cohort(ursulaAddresses, porterUri); } - public get shares(): number { + public static fromUrsulas(ursulas: ChecksumAddress[], porterUri: string) { + return new Cohort(ursulas, porterUri); + } + public get numUrsulas(): number { return this.ursulaAddresses.length; } @@ -58,26 +41,14 @@ export class Cohort { return Cohort.fromObj(config); } - public static fromObj({ - ursulaAddresses, - threshold, - shares, - porterUri, - }: CohortJSON) { - const config = { - threshold: threshold, - shares: shares, - porterUri: porterUri, - }; - return new Cohort(ursulaAddresses, config); + public static fromObj({ ursulaAddresses, porterUri }: CohortJSON) { + return new Cohort(ursulaAddresses, porterUri); } public toObj(): CohortJSON { return { ursulaAddresses: this.ursulaAddresses, - threshold: this.configuration.threshold, - shares: this.configuration.shares, - porterUri: this.configuration.porterUri, + porterUri: this.porterUri, }; } diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts index 991eb80d4..83f61365d 100644 --- a/src/sdk/strategy/cbd-strategy.ts +++ b/src/sdk/strategy/cbd-strategy.ts @@ -1,10 +1,9 @@ import { DkgPublicKey } from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; -import { bytesEqual } from '../../../test/utils'; import { - CbdTDecDecrypter, - CbdTDecDecrypterJSON, + ThresholdDecrypter, + ThresholdDecrypterJSON, } from '../../characters/cbd-recipient'; import { Enrico } from '../../characters/enrico'; import { ConditionExpression, ConditionExpressionJSON } from '../../conditions'; @@ -18,7 +17,7 @@ export type CbdStrategyJSON = { }; export type DeployedStrategyJSON = { - decrypter: CbdTDecDecrypterJSON; + decrypter: ThresholdDecrypterJSON; dkgPublicKey: Uint8Array; }; @@ -45,7 +44,7 @@ export class CbdStrategy { throw new Error('Ritual ID is undefined'); } const dkgRitual = await DkgClient.getExistingRitual(provider, ritualId); - return DeployedCbdStrategy.create(this.cohort, dkgRitual); + return DeployedCbdStrategy.create(dkgRitual, this.cohort.porterUri); } public static fromJSON(json: string) { @@ -73,18 +72,25 @@ export class CbdStrategy { export class DeployedCbdStrategy { private constructor( - public readonly decrypter: CbdTDecDecrypter, + public readonly decrypter: ThresholdDecrypter, public readonly dkgPublicKey: DkgPublicKey ) {} - public static create(cohort: Cohort, dkgRitual: DkgRitual) { - const decrypter = CbdTDecDecrypter.create( - cohort.configuration.porterUri, - dkgRitual - ); + public static create(dkgRitual: DkgRitual, porterUri: string) { + const decrypter = ThresholdDecrypter.create(porterUri, dkgRitual); return new DeployedCbdStrategy(decrypter, dkgRitual.dkgPublicKey); } + // TODO: This is analogous to create() above, is it useful? + public static async fromRitualId( + provider: ethers.providers.Web3Provider, + porterUri: string, + ritualId: number + ): Promise { + const dkgRitual = await DkgClient.getExistingRitual(provider, ritualId); + return DeployedCbdStrategy.create(dkgRitual, porterUri); + } + public makeEncrypter(conditionExpr: ConditionExpression): Enrico { return new Enrico(this.dkgPublicKey, undefined, conditionExpr); } @@ -100,7 +106,7 @@ export class DeployedCbdStrategy { private static fromObj({ decrypter, dkgPublicKey }: DeployedStrategyJSON) { return new DeployedCbdStrategy( - CbdTDecDecrypter.fromObj(decrypter), + ThresholdDecrypter.fromObj(decrypter), DkgPublicKey.fromBytes(dkgPublicKey) ); } @@ -113,9 +119,9 @@ export class DeployedCbdStrategy { } public equals(other: DeployedCbdStrategy) { - return ( - this.decrypter.equals(other.decrypter) && - bytesEqual(this.dkgPublicKey.toBytes(), other.dkgPublicKey.toBytes()) - ); + return [ + this.decrypter.equals(other.decrypter), + this.dkgPublicKey.equals(other.dkgPublicKey), + ].every(Boolean); } } diff --git a/src/sdk/strategy/pre-strategy.ts b/src/sdk/strategy/pre-strategy.ts index acc6d997f..267f0480d 100644 --- a/src/sdk/strategy/pre-strategy.ts +++ b/src/sdk/strategy/pre-strategy.ts @@ -4,13 +4,10 @@ import { ethers } from 'ethers'; import { Alice } from '../../characters/alice'; import { Bob } from '../../characters/bob'; import { Enrico } from '../../characters/enrico'; -import { - PreTDecDecrypter, - PreTDecDecrypterJSON, -} from '../../characters/pre-recipient'; +import { PreDecrypter, PreDecrypterJSON } from '../../characters/pre-recipient'; import { ConditionExpression } from '../../conditions'; import { EnactedPolicy } from '../../policies/policy'; -import { base64ToU8Receiver, bytesEquals, toJSON } from '../../utils'; +import { base64ToU8Receiver, toJSON } from '../../utils'; import { Cohort, CohortJSON } from '../cohort'; export type PreStrategyJSON = { @@ -23,7 +20,7 @@ export type PreStrategyJSON = { export type DeployedPreStrategyJSON = { cohortConfig: CohortJSON; - decrypterJSON: PreTDecDecrypterJSON; + decrypterJSON: PreDecrypterJSON; policyKeyBytes: Uint8Array; }; @@ -65,26 +62,34 @@ export class PreStrategy { } public async deploy( + web3Provider: ethers.providers.Web3Provider, label: string, - provider: ethers.providers.Web3Provider + threshold = Math.floor(this.cohort.numUrsulas / 2) + 1, + shares = this.cohort.numUrsulas ): Promise { - const porterUri = this.cohort.configuration.porterUri; - const configuration = { porterUri }; - const alice = Alice.fromSecretKey( - configuration, - this.aliceSecretKey, - provider - ); - const bob = new Bob(configuration, this.bobSecretKey); + if (shares > this.cohort.numUrsulas) { + throw new Error( + `Threshold ${threshold} cannot be greater than the number of Ursulas in the cohort ${this.cohort.numUrsulas}` + ); + } + + const porterUri = this.cohort.porterUri; + const alice = Alice.fromSecretKey(this.aliceSecretKey); + const bob = new Bob(this.bobSecretKey); const policyParams = { bob, label, - threshold: this.cohort.configuration.threshold, - shares: this.cohort.configuration.shares, + threshold, + shares, startDate: this.startDate, endDate: this.endDate, }; - const policy = await alice.grant(policyParams, this.cohort.ursulaAddresses); + const policy = await alice.grant( + web3Provider, + porterUri, + policyParams, + this.cohort.ursulaAddresses + ); return DeployedPreStrategy.create(this.cohort, policy, this.bobSecretKey); } @@ -126,27 +131,20 @@ export class PreStrategy { } public equals(other: PreStrategy) { - return ( - this.cohort.equals(other.cohort) && - // TODO: Replace with `equals` after https://github.com/nucypher/nucypher-core/issues/56 is fixed - bytesEquals( - this.aliceSecretKey.toBEBytes(), - other.aliceSecretKey.toBEBytes() - ) && - bytesEquals( - this.bobSecretKey.toBEBytes(), - other.bobSecretKey.toBEBytes() - ) && - this.startDate.toString() === other.startDate.toString() && - this.endDate.toString() === other.endDate.toString() - ); + return [ + this.cohort.equals(other.cohort), + this.aliceSecretKey.equals(other.aliceSecretKey), + this.bobSecretKey.equals(other.bobSecretKey), + this.startDate.toString() === other.startDate.toString(), + this.endDate.toString() === other.endDate.toString(), + ].every(Boolean); } } export class DeployedPreStrategy { private constructor( public readonly cohort: Cohort, - public readonly decrypter: PreTDecDecrypter, + public readonly decrypter: PreDecrypter, public readonly policyKey: PublicKey ) {} @@ -155,8 +153,8 @@ export class DeployedPreStrategy { policy: EnactedPolicy, bobSecretKey: SecretKey ) { - const decrypter = PreTDecDecrypter.create( - cohort.configuration.porterUri, + const decrypter = PreDecrypter.create( + cohort.porterUri, bobSecretKey, policy.policyKey, policy.aliceVerifyingKey, @@ -184,7 +182,7 @@ export class DeployedPreStrategy { policyKeyBytes, }: DeployedPreStrategyJSON) { const cohort = Cohort.fromObj(cohortConfig); - const decrypter = PreTDecDecrypter.fromObj(decrypterJSON); + const decrypter = PreDecrypter.fromObj(decrypterJSON); const policyKey = PublicKey.fromCompressedBytes(policyKeyBytes); return new DeployedPreStrategy(cohort, decrypter, policyKey); } @@ -198,10 +196,10 @@ export class DeployedPreStrategy { } public equals(other: DeployedPreStrategy) { - return ( - this.cohort.equals(other.cohort) && - this.decrypter.equals(other.decrypter) && - this.policyKey.equals(other.policyKey) - ); + return [ + this.cohort.equals(other.cohort), + this.decrypter.equals(other.decrypter), + this.policyKey.equals(other.policyKey), + ].every(Boolean); } } diff --git a/test/acceptance/alice-grants.test.ts b/test/acceptance/alice-grants.test.ts index 5460d43b3..9600e7582 100644 --- a/test/acceptance/alice-grants.test.ts +++ b/test/acceptance/alice-grants.test.ts @@ -6,15 +6,17 @@ import { } from '@nucypher/nucypher-core'; import { EnactedPolicy, Enrico, MessageKit } from '../../src'; -import { Ursula } from '../../src/characters/porter'; +import { Ursula } from '../../src/porter'; import { ChecksumAddress } from '../../src/types'; import { toBytes } from '../../src/utils'; import { bytesEqual, fakeAlice, fakeBob, + fakePorterUri, fakeRemoteBob, fakeUrsulas, + fakeWeb3Provider, fromBytes, mockEncryptTreasureMap, mockGenerateKFrags, @@ -32,6 +34,7 @@ describe('story: alice shares message with bob through policy', () => { const startDate = new Date(); const endDate = new Date(Date.now() + 60 * 1000); const mockedUrsulas = fakeUrsulas(shares); + const web3Provider = fakeWeb3Provider(); // Intermediate variables used for mocking let encryptedTreasureMap: EncryptedTreasureMap; @@ -62,7 +65,7 @@ describe('story: alice shares message with bob through policy', () => { startDate, endDate, }; - policy = await alice.grant(policyParams); + policy = await alice.grant(web3Provider, fakePorterUri, policyParams); expect( bytesEqual( @@ -104,6 +107,7 @@ describe('story: alice shares message with bob through policy', () => { ); const retrievedMessage = await bob.retrieveAndDecrypt( + fakePorterUri, policyEncryptingKey, aliceVerifyingKey, [encryptedMessage], diff --git a/test/acceptance/delay-enact.test.ts b/test/acceptance/delay-enact.test.ts index 857e0e9f0..ac0183072 100644 --- a/test/acceptance/delay-enact.test.ts +++ b/test/acceptance/delay-enact.test.ts @@ -1,21 +1,24 @@ import { bytesEqual, fakeAlice, + fakePorterUri, fakeRemoteBob, fakeUrsulas, + fakeWeb3Provider, mockEncryptTreasureMap, mockGenerateKFrags, mockGetUrsulas, mockPublishToBlockchain, } from '../utils'; -describe('story: alice1 creates a policy but alice2 enacts it', () => { +describe('story: alice creates a policy but someone else enacts it', () => { const threshold = 2; const shares = 3; const startDate = new Date(); const endDate = new Date(Date.now() + 60 * 1000); // 60s later const mockedUrsulas = fakeUrsulas(shares); const label = 'fake-data-label'; + const web3Provider = fakeWeb3Provider(); it('alice generates a new policy', async () => { const getUrsulasSpy = mockGetUrsulas(mockedUrsulas); @@ -23,8 +26,7 @@ describe('story: alice1 creates a policy but alice2 enacts it', () => { const publishToBlockchainSpy = mockPublishToBlockchain(); const encryptTreasureMapSpy = mockEncryptTreasureMap(); - const alice1 = fakeAlice('fake-secret-key-32-bytes-alice-1'); - const alice2 = fakeAlice('fake-secret-key-32-bytes-alice-2'); + const alice = fakeAlice('fake-secret-key-32-bytes-alice-1'); const bob = fakeRemoteBob(); const policyParams = { bob, @@ -35,18 +37,20 @@ describe('story: alice1 creates a policy but alice2 enacts it', () => { endDate, }; - const preEnactedPolicy = await alice1.generatePreEnactedPolicy( + const preEnactedPolicy = await alice.generatePreEnactedPolicy( + web3Provider, + fakePorterUri, policyParams ); expect( bytesEqual( preEnactedPolicy.aliceVerifyingKey.toCompressedBytes(), - alice1.verifyingKey.toCompressedBytes() + alice.verifyingKey.toCompressedBytes() ) ).toBeTruthy(); expect(preEnactedPolicy.label).toBe(label); - const enacted = await preEnactedPolicy.enact(alice2); + const enacted = await preEnactedPolicy.enact(web3Provider); expect(enacted.txHash).toBeDefined(); expect(getUrsulasSpy).toHaveBeenCalled(); diff --git a/test/docs/cbd.test.ts b/test/docs/cbd.test.ts index 7538ba827..e657827c7 100644 --- a/test/docs/cbd.test.ts +++ b/test/docs/cbd.test.ts @@ -1,8 +1,14 @@ import { MessageKit, VerifiedKeyFrag } from '@nucypher/nucypher-core'; import { providers } from 'ethers'; -import { Cohort, conditions, PreStrategy, SecretKey } from '../../src'; -import { Ursula } from '../../src/characters/porter'; +import { + Cohort, + conditions, + getPorterUri, + PreStrategy, + SecretKey, +} from '../../src'; +import { Ursula } from '../../src/porter'; import { toBytes } from '../../src/utils'; import { fakeUrsulas, @@ -60,12 +66,9 @@ describe('Get Started (CBD PoC)', () => { // // 2. Build a Cohort - const config = { - threshold: 2, - shares: 4, - porterUri: 'https://porter-tapir.nucypher.community', - }; - const newCohort = await Cohort.create(config); + const porterUri = getPorterUri('tapir'); + const numUrsulas = 5; + const newCohort = await Cohort.create(porterUri, numUrsulas); // 3. Specify default conditions const NFTOwnership = new ERC721Ownership({ @@ -86,7 +89,7 @@ describe('Get Started (CBD PoC)', () => { const mumbai = providers.getNetwork(80001); const web3Provider = new providers.Web3Provider(MMprovider, mumbai); - const newDeployed = await newStrategy.deploy('test', web3Provider); + const newDeployed = await newStrategy.deploy(web3Provider, 'test'); // 5. Encrypt the plaintext & update conditions const NFTBalanceConfig = { diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts index 859868816..9725b9d87 100644 --- a/test/unit/cbd-strategy.test.ts +++ b/test/unit/cbd-strategy.test.ts @@ -3,7 +3,7 @@ import { SecretKey, SessionStaticSecret } from '@nucypher/nucypher-core'; import { conditions } from '../../src'; import { FerveoVariant } from '../../src'; import { CbdStrategy, DeployedCbdStrategy } from '../../src'; -import { CbdTDecDecrypter } from '../../src/characters/cbd-recipient'; +import { ThresholdDecrypter } from '../../src/characters/cbd-recipient'; import { toBytes } from '../../src/utils'; import { fakeDkgFlow, @@ -163,18 +163,18 @@ describe('CbdDeployedStrategy', () => { }); }); -describe('CbdTDecDecrypter', () => { +describe('ThresholdDecrypter', () => { it('serializes to a plain object', async () => { const { deployedStrategy } = await makeDeployedCbdStrategy(); const configObj = deployedStrategy.decrypter.toObj(); - const fromObj = CbdTDecDecrypter.fromObj(configObj); + const fromObj = ThresholdDecrypter.fromObj(configObj); expect(fromObj.equals(deployedStrategy.decrypter)).toBeTruthy(); }); it('serializes to a JSON', async () => { const { deployedStrategy } = await makeDeployedCbdStrategy(); const configJSON = deployedStrategy.decrypter.toJSON(); - const fromJSON = CbdTDecDecrypter.fromJSON(configJSON); + const fromJSON = ThresholdDecrypter.fromJSON(configJSON); expect(fromJSON.equals(deployedStrategy.decrypter)).toBeTruthy(); }); }); diff --git a/test/unit/pre-strategy.test.ts b/test/unit/pre-strategy.test.ts index 6a5e6d2f3..daee95dae 100644 --- a/test/unit/pre-strategy.test.ts +++ b/test/unit/pre-strategy.test.ts @@ -3,10 +3,10 @@ import { SecretKey, VerifiedKeyFrag } from '@nucypher/nucypher-core'; import { conditions, DeployedPreStrategy, + PreDecrypter, PreStrategy, - PreTDecDecrypter, } from '../../src'; -import { Ursula } from '../../src/characters/porter'; +import { Ursula } from '../../src/porter'; import { toBytes } from '../../src/utils'; import { fakeUrsulas, @@ -54,7 +54,7 @@ const makeDeployedPreStrategy = async () => { const makeTreasureMapSpy = mockMakeTreasureMap(); const encryptTreasureMapSpy = mockEncryptTreasureMap(); - const deployedStrategy = await strategy.deploy('test', aliceProvider); + const deployedStrategy = await strategy.deploy(aliceProvider, 'test'); expect(generateKFragsSpy).toHaveBeenCalled(); expect(publishToBlockchainSpy).toHaveBeenCalled(); @@ -151,18 +151,18 @@ describe('PreDeployedStrategy', () => { }); }); -describe('PreTDecDecrypter', () => { +describe('PreDecrypter', () => { it('serializes to a plain object', async () => { const { deployedStrategy } = await makeDeployedPreStrategy(); const asObj = deployedStrategy.decrypter.toObj(); - const fromJson = PreTDecDecrypter.fromObj(asObj); + const fromJson = PreDecrypter.fromObj(asObj); expect(fromJson.equals(deployedStrategy.decrypter)).toBeTruthy(); }); it('serializes to JSON', async () => { const { deployedStrategy } = await makeDeployedPreStrategy(); const asJson = deployedStrategy.decrypter.toJSON(); - const fromJson = PreTDecDecrypter.fromJSON(asJson); + const fromJson = PreDecrypter.fromJSON(asJson); expect(fromJson.equals(deployedStrategy.decrypter)).toBeTruthy(); }); }); diff --git a/test/utils.ts b/test/utils.ts index b4095fbe8..b9b76db06 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -37,22 +37,22 @@ import axios from 'axios'; import { ethers, providers, Wallet } from 'ethers'; import { keccak256 } from 'ethers/lib/utils'; -import { Alice, Bob, Cohort, Configuration, RemoteBob } from '../src'; +import { Alice, Bob, Cohort, RemoteBob } from '../src'; import { DkgCoordinatorAgent, DkgParticipant, DkgRitualState, } from '../src/agents/coordinator'; -import { CbdTDecDecrypter } from '../src/characters/cbd-recipient'; +import { ThresholdDecrypter } from '../src/characters/cbd-recipient'; +import { DkgClient, DkgRitual } from '../src/dkg'; +import { BlockchainPolicy, PreEnactedPolicy } from '../src/policies/policy'; import { CbdDecryptResult, GetUrsulasResult, - Porter, + PorterClient, RetrieveCFragsResult, Ursula, -} from '../src/characters/porter'; -import { DkgClient, DkgRitual } from '../src/dkg'; -import { BlockchainPolicy, PreEnactedPolicy } from '../src/policies/policy'; +} from '../src/porter'; import { ChecksumAddress } from '../src/types'; import { toBytes, toHexString, zip } from '../src/utils'; @@ -63,15 +63,13 @@ export const bytesEqual = (first: Uint8Array, second: Uint8Array): boolean => export const fromBytes = (bytes: Uint8Array): string => new TextDecoder().decode(bytes); -const mockConfig: Configuration = { - porterUri: 'https://_this_should_crash.com/', -}; +export const fakePorterUri = 'https://_this_should_crash.com/'; export const fakeBob = (): Bob => { const secretKey = SecretKey.fromBEBytes( toBytes('fake-secret-key-32-bytes-bob-xxx') ); - return Bob.fromSecretKey(mockConfig, secretKey); + return Bob.fromSecretKey(secretKey); }; export const fakeRemoteBob = (): RemoteBob => { @@ -81,8 +79,7 @@ export const fakeRemoteBob = (): RemoteBob => { export const fakeAlice = (aliceKey = 'fake-secret-key-32-bytes-alice-x') => { const secretKey = SecretKey.fromBEBytes(toBytes(aliceKey)); - const provider = fakeWeb3Provider(secretKey.toBEBytes()); - return Alice.fromSecretKey(mockConfig, secretKey, provider); + return Alice.fromSecretKey(secretKey); }; export const fakeWeb3Provider = ( @@ -168,7 +165,7 @@ export const mockRetrieveCFragsRequest = ( ) => { const results = fakeCFragResponse(ursulas, verifiedKFrags, capsule); return jest - .spyOn(Porter.prototype, 'retrieveCFrags') + .spyOn(PorterClient.prototype, 'retrieveCFrags') .mockImplementation(() => { return Promise.resolve(results); }); @@ -492,14 +489,16 @@ export const mockCbdDecrypt = ( encryptedResponses, errors, }; - return jest.spyOn(Porter.prototype, 'cbdDecrypt').mockImplementation(() => { - return Promise.resolve(result); - }); + return jest + .spyOn(PorterClient.prototype, 'cbdDecrypt') + .mockImplementation(() => { + return Promise.resolve(result); + }); }; export const mockRandomSessionStaticSecret = (secret: SessionStaticSecret) => { return jest - .spyOn(CbdTDecDecrypter.prototype as any, 'makeSessionKey') + .spyOn(ThresholdDecrypter.prototype as any, 'makeSessionKey') .mockImplementation(() => secret); }; @@ -535,12 +534,9 @@ export const mockGetExistingRitual = (dkgRitual: DkgRitual) => { export const makeCohort = async (ursulas: Ursula[]) => { const getUrsulasSpy = mockGetUrsulas(ursulas); - const config = { - threshold: 2, - shares: ursulas.length, - porterUri: 'https://_this.should.crash', - }; - const cohort = await Cohort.create(config); + const porterUri = 'https://_this.should.crash'; + const numUrsulas = ursulas.length; + const cohort = await Cohort.create(porterUri, numUrsulas); expect(getUrsulasSpy).toHaveBeenCalled(); return cohort; };