Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fix missing edge case in select and use euint256 for tickets #17

Merged
merged 1 commit into from
Dec 22, 2024
Merged
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
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