Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/test-suite-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ jobs:
run: |
./fhevm-cli test public-decrypt-http-mixed

- name: Negative ACL tests
working-directory: test-suite/fhevm
run: |
./fhevm-cli test negative-acl

- name: Random operators test (subset)
working-directory: test-suite/fhevm
run: |
Expand Down
286 changes: 144 additions & 142 deletions test-suite/e2e/test/delegatedUserDecryption/delegatedUserDecryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,147 +200,149 @@ describe('Delegated user decryption', function () {
expect(Number(decryptedBalanceBefore) - Number(decryptedBalanceAfter)).to.equal(Number(transferAmount));
});

it('test delegated user decryption - smartWallet revokes the delegation of user decryption to an EOA', async function () {
// First, ensure Bob has delegation.
const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
const delegateTx = await this.smartWallet
.connect(this.signers.bob)
.delegateUserDecryption(
this.signers.bob.address,
this.tokenAddress,
expirationTimestamp,
);
await delegateTx.wait();

// Wait for 15 blocks to ensure delegation is propagated by the coprocessor.
const currentBlock1 = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock1 + 15);

// Revoke the delegation for Bob's EOA.
const revokeTx = await this.smartWallet
.connect(this.signers.bob)
.revokeUserDecryptionDelegation(
this.signers.bob.address,
this.tokenAddress,
);
await revokeTx.wait();

// Wait for 15 blocks to ensure revocation is propagated by the coprocessor.
const currentBlock2 = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock2 + 15);

// Try to decrypt the smartWallet balance with Bob's EOA, which should now fail.
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.bob.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.bob,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.bob.address,
this.signers.bob,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected after revocation');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
});

it('test delegated user decryption should fail when no delegation exists', async function () {
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.dave.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.dave,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.dave.address,
this.signers.dave,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected without delegation');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
});

it('test delegated user decryption should fail when delegation is for wrong contract', async function () {
const dummyFactory = await ethers.getContractFactory('UserDecrypt');
const dummy = await dummyFactory.connect(this.signers.alice).deploy();
await dummy.waitForDeployment();
const wrongAddress = await dummy.getAddress();

const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
const tx = await this.smartWallet
.connect(this.signers.bob)
.delegateUserDecryption(this.signers.eve.address, wrongAddress, expirationTimestamp);
await tx.wait();
const currentBlock = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock + 15);

const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.eve.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.eve,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.eve.address,
this.signers.eve,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected for wrong contract');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
});

it('test delegated user decryption should fail when delegation has expired', async function () {
// Expiration must be >1h from chain time (FHE library constraint).
// Use block timestamp, not Date.now(), since evm_increaseTime shifts chain clock.
const oneHour = 3600;
const buffer = 60;
const latestBlock = await ethers.provider.getBlock('latest');
const expirationTimestamp = latestBlock!.timestamp + oneHour + buffer;
const tx = await this.smartWallet
.connect(this.signers.bob)
.delegateUserDecryption(this.signers.eve.address, this.tokenAddress, expirationTimestamp);
await tx.wait();

// Fast-forward time past the expiration.
await ethers.provider.send('evm_increaseTime', [oneHour + buffer + 1]);
await ethers.provider.send('evm_mine', []);

const currentBlock = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock + 15);

const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.eve.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.eve,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.eve.address,
this.signers.eve,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected for expired delegation');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
describe('negative-acl', function () {
it('should reject when delegation has been revoked', async function () {
// First, ensure Bob has delegation.
const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
const delegateTx = await this.smartWallet
.connect(this.signers.bob)
.delegateUserDecryption(
this.signers.bob.address,
this.tokenAddress,
expirationTimestamp,
);
await delegateTx.wait();

// Wait for 15 blocks to ensure delegation is propagated by the coprocessor.
const currentBlock1 = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock1 + 15);

// Revoke the delegation for Bob's EOA.
const revokeTx = await this.smartWallet
.connect(this.signers.bob)
.revokeUserDecryptionDelegation(
this.signers.bob.address,
this.tokenAddress,
);
await revokeTx.wait();

// Wait for 15 blocks to ensure revocation is propagated by the coprocessor.
const currentBlock2 = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock2 + 15);

// Try to decrypt the smartWallet balance with Bob's EOA, which should now fail.
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.bob.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.bob,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.bob.address,
this.signers.bob,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected after revocation');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
});

it('should reject when no delegation exists', async function () {
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.dave.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.dave,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.dave.address,
this.signers.dave,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected without delegation');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
});

it('should reject when delegation is for wrong contract', async function () {
const dummyFactory = await ethers.getContractFactory('UserDecrypt');
const dummy = await dummyFactory.connect(this.signers.alice).deploy();
await dummy.waitForDeployment();
const wrongAddress = await dummy.getAddress();

const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
const tx = await this.smartWallet
.connect(this.signers.bob)
.delegateUserDecryption(this.signers.eve.address, wrongAddress, expirationTimestamp);
await tx.wait();
const currentBlock = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock + 15);

const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.eve.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.eve,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.eve.address,
this.signers.eve,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected for wrong contract');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
});

it('should reject when delegation has expired', async function () {
// Expiration must be >1h from chain time (FHE library constraint).
// Use block timestamp, not Date.now(), since evm_increaseTime shifts chain clock.
const oneHour = 3600;
const buffer = 60;
const latestBlock = await ethers.provider.getBlock('latest');
const expirationTimestamp = latestBlock!.timestamp + oneHour + buffer;
const tx = await this.smartWallet
.connect(this.signers.bob)
.delegateUserDecryption(this.signers.eve.address, this.tokenAddress, expirationTimestamp);
await tx.wait();

// Fast-forward time past the expiration.
await ethers.provider.send('evm_increaseTime', [oneHour + buffer + 1]);
await ethers.provider.send('evm_mine', []);

const currentBlock = await ethers.provider.getBlockNumber();
await waitForBlock(currentBlock + 15);

const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
const { publicKey, privateKey } = this.instances.eve.generateKeypair();

try {
await delegatedUserDecryptSingleHandle(
this.instances.eve,
balanceHandle,
this.tokenAddress,
this.smartWalletAddress,
this.signers.eve.address,
this.signers.eve,
privateKey,
publicKey,
);
expect.fail('Expected delegated user decrypt to be rejected for expired delegation');
} catch (err: any) {
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
}
});
});
});
53 changes: 36 additions & 17 deletions test-suite/e2e/test/encryptedERC20/EncryptedERC20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,6 @@ describe('EncryptedERC20', function () {
);

expect(balanceBob).to.equal(1337);

// on the other hand, Bob should be unable to read Alice's balance
try {
await userDecryptSingleHandle(
balanceHandleAlice,
this.contractAddress,
this.instances.bob,
this.signers.bob,
privateKeyBob,
publicKeyBob,
);
expect.fail('Expected an error to be thrown - Bob should not be able to user decrypt Alice balance');
} catch (error) {
expect(error.message).to.equal(
`User address ${this.signers.bob.address} is not authorized to user decrypt handle ${balanceHandleAlice}!`,
);
}
});

it('should not transfer tokens between two users', async function () {
Expand Down Expand Up @@ -251,4 +234,40 @@ describe('EncryptedERC20', function () {
);
expect(balanceBob2).to.equal(1337); // check that transfer did happen this time
});

describe('negative-acl', function () {
it('should reject when user is not allowed for handle', async function () {
const transaction = await this.erc20.mint(10000);
await transaction.wait();

const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
input.add64(1337);
const encryptedTransferAmount = await input.encrypt();
const tx = await this.erc20['transfer(address,bytes32,bytes)'](
this.signers.bob.address,
encryptedTransferAmount.handles[0],
encryptedTransferAmount.inputProof,
);
await tx.wait();

const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice);
const { publicKey: publicKeyBob, privateKey: privateKeyBob } = this.instances.bob.generateKeypair();

try {
await userDecryptSingleHandle(
balanceHandleAlice,
this.contractAddress,
this.instances.bob,
this.signers.bob,
privateKeyBob,
publicKeyBob,
);
expect.fail('Expected an error to be thrown - Bob should not be able to user decrypt Alice balance');
} catch (error) {
expect(error.message).to.equal(
`User address ${this.signers.bob.address} is not authorized to user decrypt handle ${balanceHandleAlice}!`,
);
}
});
});
});
24 changes: 13 additions & 11 deletions test-suite/e2e/test/httpPublicDecrypt/httpPublicDecrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,19 @@ describe('HTTPPublicDecrypt', function () {
assert.deepEqual(res.clearValues, expectedRes);
});

it('test public decrypt should fail for non-publicly-decryptable handle', async function () {
const factory = await ethers.getContractFactory('UserDecrypt');
const contract = await factory.connect(this.signers.alice).deploy();
await contract.waitForDeployment();
const handle = await contract.xBool();
describe('negative-acl', function () {
it('should reject when handle is not publicly decryptable', async function () {
const factory = await ethers.getContractFactory('UserDecrypt');
const contract = await factory.connect(this.signers.alice).deploy();
await contract.waitForDeployment();
const handle = await contract.xUint8();

try {
await this.instances.alice.publicDecrypt([handle]);
expect.fail('Expected an error - handle is not publicly decryptable');
} catch (error) {
expect(error.message).to.include('not allowed for public decryption');
}
try {
await this.instances.alice.publicDecrypt([handle]);
expect.fail('Expected an error - handle is not publicly decryptable');
} catch (error) {
expect(error.message).to.include('not allowed for public decryption');
}
});
});
});
Loading
Loading