Skip to content

Commit

Permalink
feat: fix missing edge case in select and use euint256 for tickets (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
jatZama authored Dec 22, 2024
1 parent 4c46a78 commit 20b147e
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 35 deletions.
25 changes: 14 additions & 11 deletions hardhat/contracts/auctions/BlindAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -141,6 +143,7 @@ contract BlindAuction is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, Gatew
}
TFHE.allowThis(highestBid);
TFHE.allowThis(winningTicket);
TFHE.allowThis(userTicket);
TFHE.allow(userTicket, msg.sender);
}

Expand All @@ -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];
}

Expand All @@ -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;
}
Expand Down
44 changes: 22 additions & 22 deletions hardhat/test/blindAuction/BlindAuction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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(
Expand All @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions hardhat/test/fhevmjsMocked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
}
Expand Down

0 comments on commit 20b147e

Please sign in to comment.