From 8d77c5a6e34c9b4e5856551fb687742a06d325ba Mon Sep 17 00:00:00 2001 From: jatZama Date: Sun, 22 Dec 2024 17:40:43 +0100 Subject: [PATCH] feat: fix missing edge case in select and use euint256 for tickets --- hardhat/contracts/auctions/BlindAuction.sol | 25 ++++++------ hardhat/test/blindAuction/BlindAuction.ts | 44 ++++++++++----------- hardhat/test/fhevmjsMocked.ts | 6 ++- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/hardhat/contracts/auctions/BlindAuction.sol b/hardhat/contracts/auctions/BlindAuction.sol index e5b660f..8291210 100644 --- a/hardhat/contracts/auctions/BlindAuction.sol +++ b/hardhat/contracts/auctions/BlindAuction.sol @@ -22,16 +22,14 @@ contract BlindAuction is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, Gatew /// @notice Ticket corresponding to the highest bid /// @dev Used during reencryption to know if a user has won the bid - euint64 private winningTicket; + euint256 private winningTicket; /// @notice Decryption of winningTicket /// @dev Can be requested by anyone after auction ends - uint64 private decryptedWinningTicket; + uint256 private decryptedWinningTicket; /// @notice Ticket randomly sampled for each user - /// @dev WARNING: We assume probability of duplicated tickets is null - /// @dev An improved implementation could sample 4 random euint64 tickets per user for negligible collision probability - mapping(address account => euint64 ticket) private userTickets; + mapping(address account => euint256 ticket) private userTickets; /// @notice Mapping from bidder to their bid value mapping(address account => euint64 bidAmount) private bids; @@ -122,10 +120,14 @@ contract BlindAuction is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, Gatew TFHE.allowThis(currentBid); TFHE.allow(currentBid, msg.sender); - euint64 randTicket = TFHE.randEuint64(); - euint64 userTicket; + euint256 randTicket = TFHE.randEuint256(); + euint256 userTicket; if (TFHE.isInitialized(highestBid)) { - userTicket = TFHE.select(TFHE.ne(sentBalance, 0), randTicket, userTickets[msg.sender]); // don't update ticket if sentBalance is null (or else winner sending an additional zero bid would lose the prize) + if (TFHE.isInitialized(userTickets[msg.sender])) { + userTicket = TFHE.select(TFHE.ne(sentBalance, 0), randTicket, userTickets[msg.sender]); // don't update ticket if sentBalance is null (or else winner sending an additional zero bid would lose the prize) + } else { + userTicket = TFHE.select(TFHE.ne(sentBalance, 0), randTicket, TFHE.asEuint256(0)); + } } else { userTicket = randTicket; } @@ -141,6 +143,7 @@ contract BlindAuction is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, Gatew } TFHE.allowThis(highestBid); TFHE.allowThis(winningTicket); + TFHE.allowThis(userTicket); TFHE.allow(userTicket, msg.sender); } @@ -163,7 +166,7 @@ contract BlindAuction is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, Gatew /// @dev Can be used in a reencryption request /// @param account The address of the bidder /// @return The encrypted ticket - function ticketUser(address account) external view returns (euint64) { + function ticketUser(address account) external view returns (euint256) { return userTickets[account]; } @@ -178,14 +181,14 @@ contract BlindAuction is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, Gatew /// @notice Callback function to set the decrypted winning ticket /// @dev Can only be called by the Gateway /// @param resultDecryption The decrypted winning ticket - function setDecryptedWinningTicket(uint256, uint64 resultDecryption) public onlyGateway { + function setDecryptedWinningTicket(uint256, uint256 resultDecryption) public onlyGateway { decryptedWinningTicket = resultDecryption; } /// @notice Get the decrypted winning ticket /// @dev Can only be called after the winning ticket has been decrypted - if `userTickets[account]` is an encryption of decryptedWinningTicket, then `account` won and can call `claim` succesfully /// @return The decrypted winning ticket - function getDecryptedWinningTicket() external view returns (uint64) { + function getDecryptedWinningTicket() external view returns (uint256) { require(decryptedWinningTicket != 0, "Winning ticket has not been decrypted yet"); return decryptedWinningTicket; } diff --git a/hardhat/test/blindAuction/BlindAuction.ts b/hardhat/test/blindAuction/BlindAuction.ts index e268058..4592e76 100644 --- a/hardhat/test/blindAuction/BlindAuction.ts +++ b/hardhat/test/blindAuction/BlindAuction.ts @@ -3,7 +3,7 @@ import { ethers } from "hardhat"; import { awaitAllDecryptionResults, initGateway } from "../asyncDecrypt"; import { createInstance } from "../instance"; -import { reencryptEuint64 } from "../reencrypt"; +import { reencryptEuint64, reencryptEuint256 } from "../reencrypt"; import { getSigners, initSigners } from "../signers"; import { deployBlindAuctionFixture } from "./BlindAuction.fixture"; import { deployConfidentialERC20Fixture } from "./ConfidentialERC20.fixture"; @@ -87,10 +87,10 @@ describe("BlindAuction", function () { input3.add64(10); const bobBidAmount_auction = await input3.encrypt(); - // const txBobBid = await this.blindAuction - // .connect(this.signers.bob) - // .bid(bobBidAmount_auction.handles[0], bobBidAmount_auction.inputProof, { gasLimit: 5000000 }); - // txBobBid.wait(); + const txBobBid = await this.blindAuction + .connect(this.signers.bob) + .bid(bobBidAmount_auction.handles[0], bobBidAmount_auction.inputProof, { gasLimit: 5000000 }); + txBobBid.wait(); // part 2 const input4 = this.instance.createEncryptedInput(this.contractAddress, this.signers.carol.address); @@ -107,9 +107,9 @@ describe("BlindAuction", function () { await txAliceStop.wait(); // Get and verify bids - // const bobBidHandle = await this.blindAuction.getBid(this.signers.bob.address); - // const bobBidDecrypted = await reencryptEuint64(this.signers.bob, this.instance, bobBidHandle, this.contractAddress); - // expect(bobBidDecrypted).to.equal(10); + const bobBidHandle = await this.blindAuction.getBid(this.signers.bob.address); + const bobBidDecrypted = await reencryptEuint64(this.signers.bob, this.instance, bobBidHandle, this.contractAddress); + expect(bobBidDecrypted).to.equal(10); const carolBidHandle = await this.blindAuction.getBid(this.signers.carol.address); const carolBidDecrypted = await reencryptEuint64( @@ -121,24 +121,24 @@ describe("BlindAuction", function () { expect(carolBidDecrypted).to.equal(20); // Do I have the highest bid? - // const carolHighestBidEnc = await this.blindAuction - // .connect(this.signers.carol) - // .doIHaveHighestBid(tokenCarol.publicKey, tokenCarol.signature); - // const carolHighestBidDec = this.instance.carol.decrypt(this.contractAddress, carolHighestBidEnc); - // expect(carolHighestBidDec).to.equal(1); + //const carolHighestBidEnc = await this.blindAuction + // .connect(this.signers.carol) + // .doIHaveHighestBid(tokenCarol.publicKey, tokenCarol.signature); + //const carolHighestBidDec = this.instance.carol.decrypt(this.contractAddress, carolHighestBidEnc); + //expect(carolHighestBidDec).to.equal(1); // Get and verify tickets - // const bobTicketHandle = await this.blindAuction.ticketUser(this.signers.bob.address); - // const bobTicketDecrypted = await reencryptEuint64( - // this.signers.bob, - // this.instance, - // bobTicketHandle, - // this.contractAddress, - // ); - // expect(bobTicketDecrypted).to.not.equal(0); + const bobTicketHandle = await this.blindAuction.ticketUser(this.signers.bob.address); + const bobTicketDecrypted = await reencryptEuint256( + this.signers.bob, + this.instance, + bobTicketHandle, + this.contractAddress, + ); + expect(bobTicketDecrypted).to.not.equal(0); const carolTicketHandle = await this.blindAuction.ticketUser(this.signers.carol.address); - const carolTicketDecrypted = await reencryptEuint64( + const carolTicketDecrypted = await reencryptEuint256( this.signers.carol, this.instance, carolTicketHandle, diff --git a/hardhat/test/fhevmjsMocked.ts b/hardhat/test/fhevmjsMocked.ts index 5c3f014..30a9caf 100644 --- a/hardhat/test/fhevmjsMocked.ts +++ b/hardhat/test/fhevmjsMocked.ts @@ -143,10 +143,12 @@ export const reencryptRequestMocked = async ( const acl = await hre.ethers.getContractAt(aclArtifact.abi, ACL_ADDRESS); const userAllowed = await acl.persistAllowed(handle, userAddress); const contractAllowed = await acl.persistAllowed(handle, contractAddress); - const isAllowed = userAllowed && contractAllowed; - if (!isAllowed) { + if (!userAllowed) { throw new Error("User is not authorized to reencrypt this handle!"); } + if (!contractAllowed) { + throw new Error("dApp contract is not authorized to reencrypt this handle!"); + } if (userAddress === contractAddress) { throw new Error("userAddress should not be equal to contractAddress when requesting reencryption!"); }