From cb258e444cd8ff7dd525c38ee52194f71341d2a0 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sun, 3 Sep 2023 17:44:01 +0200 Subject: [PATCH 1/5] apply pr suggestions for #273 --- src/characters/cbd-recipient.ts | 70 +++++-------------- src/characters/enrico.ts | 33 +++++---- test/integration/dkg-client.test.ts | 2 +- test/unit/cbd-strategy.test.ts | 23 +++---- test/utils.ts | 100 ++++++++++++++++++---------- 5 files changed, 112 insertions(+), 116 deletions(-) diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts index b7cba3aaa..97354ff49 100644 --- a/src/characters/cbd-recipient.ts +++ b/src/characters/cbd-recipient.ts @@ -1,26 +1,22 @@ import { - AccessControlPolicy, - AuthenticatedData, - Ciphertext, combineDecryptionSharesSimple, Context, DecryptionShareSimple, - decryptWithSharedSecret, EncryptedThresholdDecryptionRequest, EncryptedThresholdDecryptionResponse, FerveoVariant, SessionSharedSecret, SessionStaticSecret, ThresholdDecryptionRequest, + ThresholdMessageKit, } from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; -import { keccak256 } from 'ethers/lib/utils'; import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator'; import { ConditionExpression } from '../conditions'; -import { DkgClient, DkgRitual } from '../dkg'; +import { DkgRitual } from '../dkg'; import { PorterClient } from '../porter'; -import { fromJSON, toBytes, toJSON } from '../utils'; +import { fromJSON, objectEquals, toJSON } from '../utils'; export type ThresholdDecrypterJSON = { porterUri: string; @@ -46,55 +42,25 @@ export class ThresholdDecrypter { // Retrieve and decrypt ciphertext using provider and condition expression public async retrieveAndDecrypt( provider: ethers.providers.Provider, - signer: ethers.Signer, conditionExpr: ConditionExpression, - ciphertext: Ciphertext + thresholdMessageKit: ThresholdMessageKit, + signer?: ethers.Signer ): Promise { - const acp = await this.makeAcp(provider, signer, conditionExpr, ciphertext); - const decryptionShares = await this.retrieve( provider, conditionExpr, - ciphertext, - acp, + thresholdMessageKit, signer ); - const sharedSecret = combineDecryptionSharesSimple(decryptionShares); - return decryptWithSharedSecret( - ciphertext, - conditionExpr.asAad(), - sharedSecret - ); - } - - private async makeAcp( - provider: ethers.providers.Provider, - signer: ethers.Signer, - conditionExpr: ConditionExpression, - ciphertext: Ciphertext - ) { - const dkgRitual = await DkgClient.getExistingRitual( - provider, - this.ritualId - ); - const authData = new AuthenticatedData( - dkgRitual.dkgPublicKey, - conditionExpr.toWASMConditions() - ); - - const headerHash = keccak256(ciphertext.header.toBytes()); - const authorization = await signer.signMessage(headerHash); - - return new AccessControlPolicy(authData, toBytes(authorization)); + return thresholdMessageKit.decryptWithSharedSecret(sharedSecret); } // Retrieve decryption shares public async retrieve( provider: ethers.providers.Provider, conditionExpr: ConditionExpression, - ciphertext: Ciphertext, - acp: AccessControlPolicy, + thresholdMessageKit: ThresholdMessageKit, signer?: ethers.Signer ): Promise { const dkgParticipants = await DkgCoordinatorAgent.getParticipants( @@ -106,10 +72,9 @@ export class ThresholdDecrypter { .toJson(); const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests( this.ritualId, - ciphertext, - contextStr, + new Context(contextStr), dkgParticipants, - acp + thresholdMessageKit ); const { encryptedResponses, errors } = await this.porter.cbdDecrypt( @@ -154,10 +119,9 @@ export class ThresholdDecrypter { private makeDecryptionRequests( ritualId: number, - ciphertext: Ciphertext, - contextStr: string, + conditionContext: Context, dkgParticipants: Array, - acp: AccessControlPolicy + thresholdMessageKit: ThresholdMessageKit ): { sharedSecrets: Record; encryptedRequests: Record; @@ -165,9 +129,9 @@ export class ThresholdDecrypter { const decryptionRequest = new ThresholdDecryptionRequest( ritualId, FerveoVariant.simple, - ciphertext.header, - acp, - new Context(contextStr) + thresholdMessageKit.ciphertextHeader, + thresholdMessageKit.acp, + conditionContext ); const ephemeralSessionKey = this.makeSessionKey(); @@ -234,8 +198,6 @@ export class ThresholdDecrypter { } public equals(other: ThresholdDecrypter): boolean { - return ( - this.porter.porterUrl.toString() === other.porter.porterUrl.toString() - ); + return objectEquals(this.toObj(), other.toObj()); } } diff --git a/src/characters/enrico.ts b/src/characters/enrico.ts index 17c6ff358..3185ca050 100644 --- a/src/characters/enrico.ts +++ b/src/characters/enrico.ts @@ -1,11 +1,13 @@ import { - Ciphertext, + AccessControlPolicy, DkgPublicKey, - ferveoEncrypt, + encryptForDkg, MessageKit, PublicKey, SecretKey, + ThresholdMessageKit, } from '@nucypher/nucypher-core'; +import { arrayify, keccak256 } from 'ethers/lib/utils'; import { ConditionExpression } from '../conditions'; import { Keyring } from '../keyring'; @@ -51,13 +53,13 @@ export class Enrico { public encryptMessageCbd( plaintext: Uint8Array | string, - withConditions?: ConditionExpression - ): { ciphertext: Ciphertext; aad: Uint8Array } { - if (!withConditions) { - withConditions = this.conditions; + conditions?: ConditionExpression + ): ThresholdMessageKit { + if (!conditions) { + conditions = this.conditions; } - if (!withConditions) { + if (!conditions) { throw new Error('Conditions are required for CBD encryption.'); } @@ -65,12 +67,19 @@ export class Enrico { throw new Error('Wrong key type. Use encryptMessagePre instead.'); } - const aad = withConditions.asAad(); - const ciphertext = ferveoEncrypt( + const [ciphertext, authenticatedData] = encryptForDkg( plaintext instanceof Uint8Array ? plaintext : toBytes(plaintext), - aad, - this.encryptingKey + this.encryptingKey, + conditions.toWASMConditions() + ); + + const headerHash = keccak256(ciphertext.header.toBytes()); + const authorization = this.keyring.signer.sign(arrayify(headerHash)); + const acp = new AccessControlPolicy( + authenticatedData, + authorization.toBEBytes() ); - return { ciphertext, aad }; + + return new ThresholdMessageKit(ciphertext, acp); } } diff --git a/test/integration/dkg-client.test.ts b/test/integration/dkg-client.test.ts index c3eed8e8a..b2c1a6cc6 100644 --- a/test/integration/dkg-client.test.ts +++ b/test/integration/dkg-client.test.ts @@ -29,7 +29,7 @@ describe('DkgCoordinatorAgent', () => { it('fetches participants from the coordinator', async () => { const provider = fakeProvider(SecretKey.random().toBEBytes()); - const fakeParticipants = fakeDkgParticipants(fakeRitualId); + const fakeParticipants = await fakeDkgParticipants(fakeRitualId); const getParticipantsSpy = mockGetParticipants( fakeParticipants.participants ); diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts index 093dba61c..56fc7ddca 100644 --- a/test/unit/cbd-strategy.test.ts +++ b/test/unit/cbd-strategy.test.ts @@ -30,8 +30,8 @@ const { // Shared test variables const aliceSecretKey = SecretKey.fromBEBytes(aliceSecretKeyBytes); -const aliceSigner = fakeSigner(aliceSecretKey.toBEBytes()); const aliceProvider = fakeProvider(aliceSecretKey.toBEBytes()); +const aliceSigner = fakeSigner(aliceSecretKey.toBEBytes()); const ownsNFT = new ERC721Ownership({ contractAddress: '0x1e988ba4692e52Bc50b375bcC8585b95c48AaD77', parameters: [3591], @@ -54,10 +54,9 @@ async function makeDeployedCbdStrategy() { const mockedDkg = fakeDkgFlow(variant, 0, 4, 4); const mockedDkgRitual = fakeDkgRitual(mockedDkg); - const web3Provider = fakeProvider(aliceSecretKey.toBEBytes()); const getUrsulasSpy = mockGetUrsulas(ursulas); const getExistingRitualSpy = mockGetExistingRitual(mockedDkgRitual); - const deployedStrategy = await strategy.deploy(web3Provider, ritualId); + const deployedStrategy = await strategy.deploy(aliceProvider, ritualId); expect(getUrsulasSpy).toHaveBeenCalled(); expect(getExistingRitualSpy).toHaveBeenCalled(); @@ -104,20 +103,20 @@ describe('CbdDeployedStrategy', () => { const { mockedDkg, deployedStrategy } = await makeDeployedCbdStrategy(); const message = 'this is a secret'; - const { ciphertext, aad } = deployedStrategy + const thresholdMessageKit = deployedStrategy .makeEncrypter(conditionExpr) .encryptMessageCbd(message); // Setup mocks for `retrieveAndDecrypt` - const { decryptionShares } = fakeTDecFlow({ + const { decryptionShares } = await fakeTDecFlow({ ...mockedDkg, message: toBytes(message), - aad, - ciphertext, + conditionExpr, + dkgPublicKey: mockedDkg.dkg.publicKey(), + thresholdMessageKit, }); - const { participantSecrets, participants } = fakeDkgParticipants( - mockedDkg.ritualId, - variant + const { participantSecrets, participants } = await fakeDkgParticipants( + mockedDkg.ritualId ); const requesterSessionKey = SessionStaticSecret.random(); const decryptSpy = mockCbdDecrypt( @@ -133,9 +132,9 @@ describe('CbdDeployedStrategy', () => { const decryptedMessage = await deployedStrategy.decrypter.retrieveAndDecrypt( aliceProvider, - aliceSigner, conditionExpr, - ciphertext + thresholdMessageKit, + aliceSigner ); expect(getUrsulasSpy).toHaveBeenCalled(); expect(getParticipantsSpy).toHaveBeenCalled(); diff --git a/test/utils.ts b/test/utils.ts index dad688591..7a815fc1b 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -7,16 +7,15 @@ import { AggregatedTranscript, Capsule, CapsuleFrag, - Ciphertext, combineDecryptionSharesSimple, DecryptionSharePrecomputed, DecryptionShareSimple, - decryptWithSharedSecret, Dkg, + DkgPublicKey, EncryptedThresholdDecryptionResponse, EncryptedTreasureMap, + encryptForDkg, EthereumAddress, - ferveoEncrypt, FerveoVariant, Keypair, PublicKey, @@ -26,6 +25,7 @@ import { SessionStaticKey, SessionStaticSecret, ThresholdDecryptionResponse, + ThresholdMessageKit, Transcript, Validator, ValidatorMessage, @@ -36,13 +36,15 @@ import axios from 'axios'; import { ethers, providers, Wallet } from 'ethers'; import { keccak256 } from 'ethers/lib/utils'; -import { Alice, Bob, Cohort, RemoteBob } from '../src'; +import { Alice, Bob, Cohort, Enrico, RemoteBob } from '../src'; import { DkgCoordinatorAgent, DkgParticipant, DkgRitualState, } from '../src/agents/coordinator'; import { ThresholdDecrypter } from '../src/characters/cbd-recipient'; +import { ConditionExpression } from '../src/conditions'; +import { ERC721Balance } from '../src/conditions/predefined'; import { DkgClient, DkgRitual } from '../src/dkg'; import { BlockchainPolicy, PreEnactedPolicy } from '../src/policies/policy'; import { @@ -55,6 +57,8 @@ import { import { ChecksumAddress } from '../src/types'; import { toBytes, toHexString, zip } from '../src/utils'; +import { TEST_CHAIN_ID, TEST_CONTRACT_ADDR } from './unit/testVariables'; + export const bytesEqual = (first: Uint8Array, second: Uint8Array): boolean => first.length === second.length && first.every((value, index) => value === second[index]); @@ -304,23 +308,31 @@ interface FakeDkgRitualFlow { sharesNum: number; threshold: number; receivedMessages: ValidatorMessage[]; - ciphertext: Ciphertext; - aad: Uint8Array; dkg: Dkg; message: Uint8Array; + dkgPublicKey: DkgPublicKey; + conditionExpr: ConditionExpression; + thresholdMessageKit: ThresholdMessageKit; } -export const fakeTDecFlow = ({ +export const fakeTDecFlow = async ({ validators, validatorKeypairs, ritualId, sharesNum, threshold, receivedMessages, - ciphertext, - aad, message, + conditionExpr, + dkgPublicKey, + thresholdMessageKit, }: FakeDkgRitualFlow) => { + const [_ciphertext, authenticatedData] = encryptForDkg( + message, + dkgPublicKey, + conditionExpr.toWASMConditions() + ); + // Having aggregated the transcripts, the validators can now create decryption shares const decryptionShares: ( | DecryptionSharePrecomputed @@ -336,56 +348,71 @@ export const fakeTDecFlow = ({ const decryptionShare = aggregate.createDecryptionShareSimple( dkg, - ciphertext.header, - aad, + thresholdMessageKit.ciphertextHeader, + authenticatedData.aad(), keypair ); decryptionShares.push(decryptionShare); }); - // Now, the decryption share can be used to decrypt the ciphertext - // This part is in the client API const sharedSecret = combineDecryptionSharesSimple(decryptionShares); - // The client should have access to the public parameters of the DKG - const plaintext = decryptWithSharedSecret(ciphertext, aad, sharedSecret); + const plaintext = thresholdMessageKit.decryptWithSharedSecret(sharedSecret); if (!bytesEqual(plaintext, message)) { throw new Error('Decryption failed'); } - return { decryptionShares, sharedSecret, plaintext }; + return { + authenticatedData, + decryptionShares, + plaintext, + sharedSecret, + thresholdMessageKit, + }; }; -export const fakeDkgTDecFlowE2e = ( - variant: FerveoVariant, - message = toBytes('fake-message'), - aad = toBytes('fake-aad'), +const fakeConditionExpr = () => { + const erc721Balance = new ERC721Balance({ + chain: TEST_CHAIN_ID, + contractAddress: TEST_CONTRACT_ADDR, + }); + return new ConditionExpression(erc721Balance); +}; + +export const fakeDkgTDecFlowE2E = async ( ritualId = 0, + variant: FerveoVariant = FerveoVariant.precomputed, + conditionExpr: ConditionExpression = fakeConditionExpr(), + message = toBytes('fake-message'), sharesNum = 4, threshold = 4 ) => { const ritual = fakeDkgFlow(variant, ritualId, sharesNum, threshold); + const dkgPublicKey = ritual.dkg.publicKey(); + const thresholdMessageKit = new Enrico(dkgPublicKey).encryptMessageCbd( + message, + conditionExpr + ); - // In the meantime, the client creates a ciphertext and decryption request - const ciphertext = ferveoEncrypt(message, aad, ritual.dkg.publicKey()); - const { decryptionShares } = fakeTDecFlow({ + const { decryptionShares, authenticatedData } = await fakeTDecFlow({ ...ritual, - ciphertext, - aad, message, + conditionExpr, + dkgPublicKey, + thresholdMessageKit, }); return { ...ritual, message, - aad, - ciphertext, decryptionShares, + authenticatedData, + thresholdMessageKit, }; }; -export const fakeCoordinatorRitual = ( +export const fakeCoordinatorRitual = async ( ritualId: number -): { +): Promise<{ aggregationMismatch: boolean; initTimestamp: number; aggregatedTranscriptHash: string; @@ -397,8 +424,8 @@ export const fakeCoordinatorRitual = ( aggregatedTranscript: string; publicKeyHash: string; totalAggregations: number; -} => { - const ritual = fakeDkgTDecFlowE2e(FerveoVariant.precomputed); +}> => { + const ritual = await fakeDkgTDecFlowE2E(); const dkgPkBytes = ritual.dkg.publicKey().toBytes(); return { id: ritualId, @@ -418,14 +445,13 @@ export const fakeCoordinatorRitual = ( }; }; -export const fakeDkgParticipants = ( - ritualId: number, - variant = FerveoVariant.precomputed -): { +export const fakeDkgParticipants = async ( + ritualId: number +): Promise<{ participants: DkgParticipant[]; participantSecrets: Record; -} => { - const ritual = fakeDkgTDecFlowE2e(variant); +}> => { + const ritual = await fakeDkgTDecFlowE2E(ritualId); const label = toBytes(`${ritualId}`); const participantSecrets: Record = From dcc741b78db143ee77d0868d644434b611581a4d Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Wed, 6 Sep 2023 10:30:43 +0200 Subject: [PATCH 2/5] apply pr suggestions --- src/characters/cbd-recipient.ts | 36 ++++++++++----------- src/conditions/condition-expr.ts | 12 +++---- src/conditions/condition.ts | 2 +- src/conditions/context/context.ts | 35 ++++++++++++++++---- test/unit/cbd-strategy.test.ts | 1 - test/unit/conditions/condition-expr.test.ts | 7 ++++ test/unit/conditions/context.test.ts | 4 --- test/utils.ts | 15 ++------- 8 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts index 97354ff49..9f9b11bd7 100644 --- a/src/characters/cbd-recipient.ts +++ b/src/characters/cbd-recipient.ts @@ -13,7 +13,7 @@ import { import { ethers } from 'ethers'; import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator'; -import { ConditionExpression } from '../conditions'; +import { ConditionContext } from '../conditions'; import { DkgRitual } from '../dkg'; import { PorterClient } from '../porter'; import { fromJSON, objectEquals, toJSON } from '../utils'; @@ -42,13 +42,11 @@ export class ThresholdDecrypter { // Retrieve and decrypt ciphertext using provider and condition expression public async retrieveAndDecrypt( provider: ethers.providers.Provider, - conditionExpr: ConditionExpression, thresholdMessageKit: ThresholdMessageKit, signer?: ethers.Signer ): Promise { const decryptionShares = await this.retrieve( provider, - conditionExpr, thresholdMessageKit, signer ); @@ -59,7 +57,6 @@ export class ThresholdDecrypter { // Retrieve decryption shares public async retrieve( provider: ethers.providers.Provider, - conditionExpr: ConditionExpression, thresholdMessageKit: ThresholdMessageKit, signer?: ethers.Signer ): Promise { @@ -67,15 +64,18 @@ export class ThresholdDecrypter { provider, this.ritualId ); - const contextStr = await conditionExpr - .buildContext(provider, {}, signer) - .toJson(); - const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests( - this.ritualId, - new Context(contextStr), - dkgParticipants, - thresholdMessageKit - ); + const wasmContext = await ConditionContext.fromAccessControlPolicy( + provider, + thresholdMessageKit.acp, + signer + ).toWASMContext(); + const { sharedSecrets, encryptedRequests } = + await this.makeDecryptionRequests( + this.ritualId, + wasmContext, + dkgParticipants, + thresholdMessageKit + ); const { encryptedResponses, errors } = await this.porter.cbdDecrypt( encryptedRequests, @@ -117,21 +117,21 @@ export class ThresholdDecrypter { ); } - private makeDecryptionRequests( + private async makeDecryptionRequests( ritualId: number, - conditionContext: Context, + wasmContext: Context, dkgParticipants: Array, thresholdMessageKit: ThresholdMessageKit - ): { + ): Promise<{ sharedSecrets: Record; encryptedRequests: Record; - } { + }> { const decryptionRequest = new ThresholdDecryptionRequest( ritualId, FerveoVariant.simple, thresholdMessageKit.ciphertextHeader, thresholdMessageKit.acp, - conditionContext + wasmContext ); const ephemeralSessionKey = this.makeSessionKey(); diff --git a/src/conditions/condition-expr.ts b/src/conditions/condition-expr.ts index 697687088..ee12d7ccb 100644 --- a/src/conditions/condition-expr.ts +++ b/src/conditions/condition-expr.ts @@ -2,7 +2,7 @@ import { Conditions as WASMConditions } from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; import { SemVer } from 'semver'; -import { toBytes, toJSON } from '../utils'; +import { toJSON } from '../utils'; import { Condition } from './condition'; import { ConditionContext, CustomContextParam } from './context'; @@ -13,7 +13,7 @@ export type ConditionExpressionJSON = { }; export class ConditionExpression { - static VERSION = '1.0.0'; + public static VERSION = '1.0.0'; constructor( public readonly condition: Condition, @@ -61,6 +61,10 @@ export class ConditionExpression { return new WASMConditions(toJSON(this.toObj())); } + public static fromWASMConditions(conditions: WASMConditions) { + return ConditionExpression.fromJSON(conditions.toString()); + } + public buildContext( provider: ethers.providers.Provider, customParameters: Record = {}, @@ -78,10 +82,6 @@ export class ConditionExpression { return this.condition.requiresSigner(); } - public asAad(): Uint8Array { - return toBytes(this.toJson()); - } - public equals(other: ConditionExpression): boolean { return [ this.version === other.version, diff --git a/src/conditions/condition.ts b/src/conditions/condition.ts index b16444ef1..26f08618d 100644 --- a/src/conditions/condition.ts +++ b/src/conditions/condition.ts @@ -75,6 +75,6 @@ export class Condition { } public equals(other: Condition) { - return objectEquals(this, other); + return objectEquals(this.toObj(), other.toObj()); } } diff --git a/src/conditions/context/context.ts b/src/conditions/context/context.ts index b32e5a4e1..a71e90d91 100644 --- a/src/conditions/context/context.ts +++ b/src/conditions/context/context.ts @@ -1,8 +1,13 @@ -import { Conditions as WASMConditions } from '@nucypher/nucypher-core'; +import { + AccessControlPolicy, + Context, + Conditions as WASMConditions, +} from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; import { fromJSON, toJSON } from '../../utils'; import { Condition } from '../condition'; +import { ConditionExpression } from '../condition-expr'; import { USER_ADDRESS_PARAM } from '../const'; import { TypedSignature, WalletAuthenticationProvider } from './providers'; @@ -31,7 +36,7 @@ export class ConditionContext { this.validate(); } - public requiresSigner(): boolean { + private requiresSigner(): boolean { return this.conditions.some((cond) => cond.requiresSigner()); } @@ -120,19 +125,35 @@ export class ConditionContext { return parameters; }; - public toJson = async (): Promise => { + public async toJson(): Promise { const parameters = await this.toObj(); return toJSON(parameters); - }; + } - public withCustomParams = ( + public withCustomParams( params: Record - ): ConditionContext => { + ): ConditionContext { return new ConditionContext( this.provider, this.conditions, params, this.signer ); - }; + } + + public async toWASMContext(): Promise { + const asJson = await this.toJson(); + return new Context(asJson); + } + + public static fromAccessControlPolicy( + provider: ethers.providers.Provider, + acp: AccessControlPolicy, + signer?: ethers.Signer + ): ConditionContext { + const conditions = acp.conditions + ? [ConditionExpression.fromWASMConditions(acp.conditions).condition] + : []; + return new ConditionContext(provider, conditions, {}, signer); + } } diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts index 56fc7ddca..bbba1606f 100644 --- a/test/unit/cbd-strategy.test.ts +++ b/test/unit/cbd-strategy.test.ts @@ -132,7 +132,6 @@ describe('CbdDeployedStrategy', () => { const decryptedMessage = await deployedStrategy.decrypter.retrieveAndDecrypt( aliceProvider, - conditionExpr, thresholdMessageKit, aliceSigner ); diff --git a/test/unit/conditions/condition-expr.test.ts b/test/unit/conditions/condition-expr.test.ts index ff53730e1..f7d11fe2c 100644 --- a/test/unit/conditions/condition-expr.test.ts +++ b/test/unit/conditions/condition-expr.test.ts @@ -188,6 +188,13 @@ describe('condition set', () => { expect(conditionExprFromJson.equals(conditionExprFromJson)).toBeTruthy(); }); + it('serializes to and from WASM conditions', () => { + const conditionExpr = new ConditionExpression(erc721BalanceCondition); + const wasmConditions = conditionExpr.toWASMConditions(); + const fromWasm = ConditionExpression.fromWASMConditions(wasmConditions); + expect(conditionExpr.equals(fromWasm)).toBeTruthy(); + }); + it('incompatible version', () => { const currentVersion = new SemVer(ConditionExpression.VERSION); const invalidVersion = currentVersion.inc('major'); diff --git a/test/unit/conditions/context.test.ts b/test/unit/conditions/context.test.ts index 3f0f9e501..124fd3e07 100644 --- a/test/unit/conditions/context.test.ts +++ b/test/unit/conditions/context.test.ts @@ -90,20 +90,16 @@ describe('context parameters', () => { }; const condition = new ContractCondition(conditionObj); const conditionExpr = new ConditionExpression(condition); - const conditionContext = conditionExpr.buildContext(provider, {}, signer); expect(conditionExpr.contextRequiresSigner()).toBe(true); - expect(conditionContext.requiresSigner()).toBe(true); }); it('detects if a signer is not required', () => { const condition = new RpcCondition(testRpcConditionObj); const conditionExpr = new ConditionExpression(condition); - const conditionContext = conditionExpr.buildContext(provider, {}, signer); expect(JSON.stringify(condition.toObj()).includes(USER_ADDRESS_PARAM)).toBe( false ); expect(conditionExpr.contextRequiresSigner()).toBe(false); - expect(conditionContext.requiresSigner()).toBe(false); }); describe('custom method parameters', () => { diff --git a/test/utils.ts b/test/utils.ts index 7a815fc1b..b40e63ae9 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -14,7 +14,6 @@ import { DkgPublicKey, EncryptedThresholdDecryptionResponse, EncryptedTreasureMap, - encryptForDkg, EthereumAddress, FerveoVariant, Keypair, @@ -323,16 +322,8 @@ export const fakeTDecFlow = async ({ threshold, receivedMessages, message, - conditionExpr, - dkgPublicKey, thresholdMessageKit, }: FakeDkgRitualFlow) => { - const [_ciphertext, authenticatedData] = encryptForDkg( - message, - dkgPublicKey, - conditionExpr.toWASMConditions() - ); - // Having aggregated the transcripts, the validators can now create decryption shares const decryptionShares: ( | DecryptionSharePrecomputed @@ -349,7 +340,7 @@ export const fakeTDecFlow = async ({ const decryptionShare = aggregate.createDecryptionShareSimple( dkg, thresholdMessageKit.ciphertextHeader, - authenticatedData.aad(), + thresholdMessageKit.acp.aad(), keypair ); decryptionShares.push(decryptionShare); @@ -362,7 +353,6 @@ export const fakeTDecFlow = async ({ throw new Error('Decryption failed'); } return { - authenticatedData, decryptionShares, plaintext, sharedSecret, @@ -393,7 +383,7 @@ export const fakeDkgTDecFlowE2E = async ( conditionExpr ); - const { decryptionShares, authenticatedData } = await fakeTDecFlow({ + const { decryptionShares } = await fakeTDecFlow({ ...ritual, message, conditionExpr, @@ -405,7 +395,6 @@ export const fakeDkgTDecFlowE2E = async ( ...ritual, message, decryptionShares, - authenticatedData, thresholdMessageKit, }; }; From 3511348b3a9bd8d08d5599f50f42e10433075f57 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Wed, 6 Sep 2023 15:26:36 +0200 Subject: [PATCH 3/5] apply pr suggestions --- test/unit/cbd-strategy.test.ts | 1 - test/unit/conditions/context.test.ts | 16 ++++++++++++++++ test/utils.ts | 2 -- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts index bbba1606f..7251262b1 100644 --- a/test/unit/cbd-strategy.test.ts +++ b/test/unit/cbd-strategy.test.ts @@ -111,7 +111,6 @@ describe('CbdDeployedStrategy', () => { const { decryptionShares } = await fakeTDecFlow({ ...mockedDkg, message: toBytes(message), - conditionExpr, dkgPublicKey: mockedDkg.dkg.publicKey(), thresholdMessageKit, }); diff --git a/test/unit/conditions/context.test.ts b/test/unit/conditions/context.test.ts index 124fd3e07..1cc3b19d5 100644 --- a/test/unit/conditions/context.test.ts +++ b/test/unit/conditions/context.test.ts @@ -102,6 +102,22 @@ describe('context parameters', () => { expect(conditionExpr.contextRequiresSigner()).toBe(false); }); + it('rejects on a missing signer', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: USER_ADDRESS_PARAM, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionExpr = new ConditionExpression(condition); + expect(conditionExpr.contextRequiresSigner()).toBe(true); + expect(() => conditionExpr.buildContext(provider, {}, undefined)).toThrow( + `Cannot use ${USER_ADDRESS_PARAM} as custom parameter without a signer` + ); + }); + describe('custom method parameters', () => { const contractConditionObj = { ...testContractConditionObj, diff --git a/test/utils.ts b/test/utils.ts index b40e63ae9..11f8b6714 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -310,7 +310,6 @@ interface FakeDkgRitualFlow { dkg: Dkg; message: Uint8Array; dkgPublicKey: DkgPublicKey; - conditionExpr: ConditionExpression; thresholdMessageKit: ThresholdMessageKit; } @@ -386,7 +385,6 @@ export const fakeDkgTDecFlowE2E = async ( const { decryptionShares } = await fakeTDecFlow({ ...ritual, message, - conditionExpr, dkgPublicKey, thresholdMessageKit, }); From b11f1d48dbb78240417b65a33ddb3e9ca1994350 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Wed, 6 Sep 2023 15:57:45 +0200 Subject: [PATCH 4/5] update to nucypher-core@0.13.0 --- package.json | 2 +- src/conditions/context/context.ts | 15 +++++++-------- yarn.lock | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 76fade158..d9d8ceb6b 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "prebuild": "yarn typechain" }, "dependencies": { - "@nucypher/nucypher-core": "^0.12.0", + "@nucypher/nucypher-core": "^0.13.0", "axios": "^1.5.0", "deep-equal": "^2.2.1", "ethers": "^5.7.2", diff --git a/src/conditions/context/context.ts b/src/conditions/context/context.ts index a71e90d91..be4b72162 100644 --- a/src/conditions/context/context.ts +++ b/src/conditions/context/context.ts @@ -36,10 +36,6 @@ export class ConditionContext { this.validate(); } - private requiresSigner(): boolean { - return this.conditions.some((cond) => cond.requiresSigner()); - } - private validate() { Object.keys(this.customParameters).forEach((key) => { if (RESERVED_CONTEXT_PARAMS.includes(key)) { @@ -54,7 +50,10 @@ export class ConditionContext { } }); - if (this.requiresSigner() && !this.signer) { + const conditionRequiresSigner = this.conditions.some((c) => + c.requiresSigner() + ); + if (conditionRequiresSigner && !this.signer) { throw new Error( `Cannot use ${USER_ADDRESS_PARAM} as custom parameter without a signer` ); @@ -151,9 +150,9 @@ export class ConditionContext { acp: AccessControlPolicy, signer?: ethers.Signer ): ConditionContext { - const conditions = acp.conditions - ? [ConditionExpression.fromWASMConditions(acp.conditions).condition] - : []; + const conditions = [ + ConditionExpression.fromWASMConditions(acp.conditions).condition, + ]; return new ConditionContext(provider, conditions, {}, signer); } } diff --git a/yarn.lock b/yarn.lock index 7a7ef1720..c495cb6b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1818,10 +1818,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nucypher/nucypher-core@^0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@nucypher/nucypher-core/-/nucypher-core-0.12.0.tgz#0274026f3996601994e9639c06ab79c62c8f5d7c" - integrity sha512-hEjjnTSLNqHUiIF6U02j+M8jUOBCOt5mGG78lMHoathbak15AmYX4gkdkHTg2Ssvt5o77Cwzsn5YgGM4Haf7AQ== +"@nucypher/nucypher-core@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@nucypher/nucypher-core/-/nucypher-core-0.13.0.tgz#071263931d4e9604b428ea738ebe8ee5e1f302f3" + integrity sha512-HfEbrQvngOHIn0bMAdqPIF7WzkLdb5+sMhmX7bQl2SINABQ6FGGN8G+Arb+pbkYgru5qeQ+RTTbCBwBNaxFKEg== "@sinclair/typebox@^0.27.8": version "0.27.8" From 363718fba7e9b20bc276c49162626ec921acd35d Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Fri, 8 Sep 2023 17:05:44 +0200 Subject: [PATCH 5/5] apply pr suggestions --- src/characters/cbd-recipient.ts | 4 ++-- src/conditions/context/context.ts | 20 ++++++++------------ test/unit/conditions/context.test.ts | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts index 9f9b11bd7..ad973616a 100644 --- a/src/characters/cbd-recipient.ts +++ b/src/characters/cbd-recipient.ts @@ -64,9 +64,9 @@ export class ThresholdDecrypter { provider, this.ritualId ); - const wasmContext = await ConditionContext.fromAccessControlPolicy( + const wasmContext = await ConditionContext.fromConditions( provider, - thresholdMessageKit.acp, + thresholdMessageKit.acp.conditions, signer ).toWASMContext(); const { sharedSecrets, encryptedRequests } = diff --git a/src/conditions/context/context.ts b/src/conditions/context/context.ts index be4b72162..c1d8426df 100644 --- a/src/conditions/context/context.ts +++ b/src/conditions/context/context.ts @@ -1,8 +1,4 @@ -import { - AccessControlPolicy, - Context, - Conditions as WASMConditions, -} from '@nucypher/nucypher-core'; +import { Context, Conditions as WASMConditions } from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; import { fromJSON, toJSON } from '../../utils'; @@ -55,7 +51,7 @@ export class ConditionContext { ); if (conditionRequiresSigner && !this.signer) { throw new Error( - `Cannot use ${USER_ADDRESS_PARAM} as custom parameter without a signer` + `Condition contains ${USER_ADDRESS_PARAM} context variable and requires a signer to populate` ); } @@ -96,7 +92,7 @@ export class ConditionContext { if (requestedParameters.has(USER_ADDRESS_PARAM)) { if (!this.walletAuthProvider) { throw new Error( - `Cannot use ${USER_ADDRESS_PARAM} as custom parameter without a signer` + `Condition contains ${USER_ADDRESS_PARAM} context variable and requires a signer to populate` ); } parameters[USER_ADDRESS_PARAM] = @@ -145,14 +141,14 @@ export class ConditionContext { return new Context(asJson); } - public static fromAccessControlPolicy( + public static fromConditions( provider: ethers.providers.Provider, - acp: AccessControlPolicy, + conditions: WASMConditions, signer?: ethers.Signer ): ConditionContext { - const conditions = [ - ConditionExpression.fromWASMConditions(acp.conditions).condition, + const innerConditions = [ + ConditionExpression.fromWASMConditions(conditions).condition, ]; - return new ConditionContext(provider, conditions, {}, signer); + return new ConditionContext(provider, innerConditions, {}, signer); } } diff --git a/test/unit/conditions/context.test.ts b/test/unit/conditions/context.test.ts index 1cc3b19d5..4a0815912 100644 --- a/test/unit/conditions/context.test.ts +++ b/test/unit/conditions/context.test.ts @@ -114,7 +114,7 @@ describe('context parameters', () => { const conditionExpr = new ConditionExpression(condition); expect(conditionExpr.contextRequiresSigner()).toBe(true); expect(() => conditionExpr.buildContext(provider, {}, undefined)).toThrow( - `Cannot use ${USER_ADDRESS_PARAM} as custom parameter without a signer` + `Condition contains ${USER_ADDRESS_PARAM} context variable and requires a signer to populate` ); });