diff --git a/contracts/BlindAuction.sol b/contracts/BlindAuction.sol new file mode 100644 index 0000000..9ede46a --- /dev/null +++ b/contracts/BlindAuction.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.24; + +import "fhevm/lib/TFHE.sol"; +import "fhevm/gateway/GatewayCaller.sol"; +import "@openzeppelin/contracts/access/Ownable2Step.sol"; +import "./ConfidentialERC20.sol"; + +/// @notice Main contract for the blind auction +contract BlindAuction is Ownable2Step, GatewayCaller { + /// @notice Auction end time + uint256 public endTime; + + /// @notice Address of the beneficiary + address public beneficiary; + + /// @notice Current highest bid + euint64 private highestBid; + + /// @notice Ticket corresponding to the highest bid + /// @dev Used during reencryption to know if a user has won the bid + euint64 private winningTicket; + + /// @notice Decryption of winningTicket + /// @dev Can be requested by anyone after auction ends + uint64 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; + + /// @notice Mapping from bidder to their bid value + mapping(address account => euint64 bidAmount) private bids; + + /// @notice Number of bids + uint256 public bidCounter; + + /// @notice The token contract used for encrypted bids + ConfidentialERC20 public tokenContract; + + /// @notice Flag indicating whether the auction object has been claimed + /// @dev WARNING : If there is a draw, only the first highest bidder will get the prize + /// An improved implementation could handle this case differently + ebool private objectClaimed; + + /// @notice Flag to check if the token has been transferred to the beneficiary + bool public tokenTransferred; + + /// @notice Flag to determine if the auction can be stopped manually + bool public stoppable; + + /// @notice Flag to check if the auction has been manually stopped + bool public manuallyStopped = false; + + /// @notice Error thrown when a function is called too early + /// @dev Includes the time when the function can be called + error TooEarly(uint256 time); + + /// @notice Error thrown when a function is called too late + /// @dev Includes the time after which the function cannot be called + error TooLate(uint256 time); + + /// @notice Constructor to initialize the auction + /// @param _beneficiary Address of the beneficiary who will receive the highest bid + /// @param _tokenContract Address of the EncryptedERC20 token contract used for bidding + /// @param biddingTime Duration of the auction in seconds + /// @param isStoppable Flag to determine if the auction can be stopped manually + constructor( + address _beneficiary, + ConfidentialERC20 _tokenContract, + uint256 biddingTime, + bool isStoppable + ) Ownable(msg.sender) { + beneficiary = _beneficiary; + tokenContract = _tokenContract; + endTime = block.timestamp + biddingTime; + objectClaimed = TFHE.asEbool(false); + TFHE.allow(objectClaimed,address(this)); + tokenTransferred = false; + bidCounter = 0; + stoppable = isStoppable; + } + + /// @notice Submit a bid with an encrypted value + /// @dev Transfers tokens from the bidder to the contract + /// @param encryptedValue The encrypted bid amount + /// @param inputProof Proof for the encrypted input + function bid(einput encryptedValue, bytes calldata inputProof) external onlyBeforeEnd { + euint64 value = TFHE.asEuint64(encryptedValue, inputProof); + + euint64 sentBalance; + if (TFHE.isInitialized(bids[msg.sender])) { + euint64 existingBid = bids[msg.sender]; + euint64 balanceBefore = tokenContract.balanceOf(address(this)); + ebool isHigher = TFHE.lt(existingBid, value); + euint64 toTransfer = TFHE.sub(value, existingBid); + + // Transfer only if bid is higher, also to avoid overflow from previous line + euint64 amount = TFHE.select(isHigher, toTransfer, TFHE.asEuint64(0)); + TFHE.allowTransient(amount, address(tokenContract)); + tokenContract.transferFrom(msg.sender, address(this), amount); + + euint64 balanceAfter = tokenContract.balanceOf(address(this)); + sentBalance = TFHE.sub(balanceAfter, balanceBefore); + euint64 newBid = TFHE.add(existingBid, sentBalance); + bids[msg.sender] = newBid; + } else { + bidCounter++; + euint64 balanceBefore = tokenContract.balanceOf(address(this)); + TFHE.allowTransient(value, address(tokenContract)); + tokenContract.transferFrom(msg.sender, address(this), value); + euint64 balanceAfter = tokenContract.balanceOf(address(this)); + sentBalance = TFHE.sub(balanceAfter, balanceBefore); + bids[msg.sender] = sentBalance; + } + euint64 currentBid = bids[msg.sender]; + TFHE.allow(currentBid,address(this)); + TFHE.allow(currentBid, msg.sender); + + euint64 randTicket = TFHE.randEuint64(); + euint64 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) + } else { + userTicket = randTicket; + } + userTickets[msg.sender] = userTicket; + + if (!TFHE.isInitialized(highestBid)) { + highestBid = currentBid; + winningTicket = userTicket; + } else { + ebool isNewWinner = TFHE.lt(highestBid, currentBid); + highestBid = TFHE.select(isNewWinner, currentBid, highestBid); + winningTicket = TFHE.select(isNewWinner, userTicket, winningTicket); + } + TFHE.allow(highestBid,address(this)); + TFHE.allow(winningTicket,address(this)); + TFHE.allow(userTicket, msg.sender); + } + + /// @notice Get the encrypted bid of a specific account + /// @dev Can be used in a reencryption request + /// @param account The address of the bidder + /// @return The encrypted bid amount + function getBid(address account) external view returns (euint64) { + return bids[account]; + } + + /// @notice Manually stop the auction + /// @dev Can only be called by the owner and if the auction is stoppable + function stop() external onlyOwner { + require(stoppable); + manuallyStopped = true; + } + + /// @notice Get the encrypted ticket of a specific account + /// @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) { + return userTickets[account]; + } + + /// @notice Initiate the decryption of the winning ticket + /// @dev Can only be called after the auction ends + function decryptWinningTicket() public onlyAfterEnd { + uint256[] memory cts = new uint256[](1); + cts[0] = Gateway.toUint256(winningTicket); + Gateway.requestDecryption(cts, this.setDecryptedWinningTicket.selector, 0, block.timestamp + 100, false); + } + + /// @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 { + 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) { + require(decryptedWinningTicket != 0, "Winning ticket has not been decrypted yet"); + return decryptedWinningTicket; + } + + /// @notice Claim the auction object + /// @dev Succeeds only if the caller was the first to get the highest bid + function claim() public onlyAfterEnd { + ebool canClaim = TFHE.and(TFHE.eq(winningTicket, userTickets[msg.sender]), TFHE.not(objectClaimed)); + objectClaimed = TFHE.or(canClaim, objectClaimed); + TFHE.allow(objectClaimed,address(this)); + euint64 newBid = TFHE.select(canClaim, TFHE.asEuint64(0), bids[msg.sender]); + bids[msg.sender] = newBid; + TFHE.allow(bids[msg.sender],address(this)); + TFHE.allow(bids[msg.sender], msg.sender); + } + + /// @notice Transfer the highest bid to the beneficiary + /// @dev Can only be called once after the auction ends + function auctionEnd() public onlyAfterEnd { + require(!tokenTransferred); + tokenTransferred = true; + TFHE.allowTransient(highestBid, address(tokenContract)); + tokenContract.transfer(beneficiary, highestBid); + } + + /// @notice Withdraw a bid from the auction + /// @dev Can only be called after the auction ends and by non-winning bidders + function withdraw() public onlyAfterEnd { + euint64 bidValue = bids[msg.sender]; + ebool canWithdraw = TFHE.ne(winningTicket, userTickets[msg.sender]); + euint64 amount = TFHE.select(canWithdraw, bidValue, TFHE.asEuint64(0)); + TFHE.allowTransient(amount, address(tokenContract)); + tokenContract.transfer(msg.sender, amount); + euint64 newBid = TFHE.select(canWithdraw, TFHE.asEuint64(0), bids[msg.sender]); + bids[msg.sender] = newBid; + TFHE.allow(newBid,address(this)); + TFHE.allow(newBid, msg.sender); + } + + /// @notice Modifier to ensure function is called before auction ends + /// @dev Reverts if called after the auction end time or if manually stopped + modifier onlyBeforeEnd() { + if (block.timestamp >= endTime || manuallyStopped == true) revert TooLate(endTime); + _; + } + + /// @notice Modifier to ensure function is called after auction ends + /// @dev Reverts if called before the auction end time and not manually stopped + modifier onlyAfterEnd() { + if (block.timestamp < endTime && manuallyStopped == false) revert TooEarly(endTime); + _; + } +} \ No newline at end of file diff --git a/contracts/DecrytionExample.sol b/contracts/DecrytionExample.sol new file mode 100644 index 0000000..0209fc0 --- /dev/null +++ b/contracts/DecrytionExample.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "fhevm/lib/TFHE.sol"; +import "fhevm/gateway/GatewayCaller.sol"; + +contract DecryptionExample is GatewayCaller { + // Store multiple encrypted random numbers + euint8 public randomEncryptedNumber; + euint8 public randomEncryptedNumber1; + + uint8 public randomNumber; + uint8 public randomNumber1; + + function generateRandomNumber() external { + randomEncryptedNumber = TFHE.randEuint8(); + randomEncryptedNumber1 = TFHE.randEuint8(); + + TFHE.allow(randomEncryptedNumber, address(this)); + TFHE.allow(randomEncryptedNumber1, address(this)); + + // Request decryption of the final vote tallies + uint256[] memory cts = new uint256[](2); + cts[0] = Gateway.toUint256(randomEncryptedNumber); + cts[1] = Gateway.toUint256(randomEncryptedNumber1); + + Gateway.requestDecryption(cts, this.decryptionCallback.selector, 0, block.timestamp + 100, false); + } + + function decryptionCallback( + uint256 /*requestID*/, + uint8 decryptedRandomNumber1, + uint8 decryptedRandomNumber2 + ) public onlyGateway returns (bool) { + // Update plaintext tallies with decrypted values + randomNumber = decryptedRandomNumber1; + randomNumber1 = decryptedRandomNumber2; + return true; + } +} diff --git a/contracts/HiddenRandomCards.sol b/contracts/HiddenRandomCards.sol new file mode 100644 index 0000000..17b9c2c --- /dev/null +++ b/contracts/HiddenRandomCards.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "fhevm/lib/TFHE.sol"; + +contract HiddenRandomCards { + // Store multiple encrypted random numbers + euint8 public randomEncryptedNumber; + euint8 public randomEncryptedNumber1; + euint8 public randomEncryptedNumber2; + + function generateRandomNumber() external { + randomEncryptedNumber = TFHE.randEuint8(); + randomEncryptedNumber1 = TFHE.randEuint8(); + randomEncryptedNumber2 = TFHE.randEuint8(); + + TFHE.allow(randomEncryptedNumber, address(this)); + TFHE.allow(randomEncryptedNumber1, address(this)); + TFHE.allow(randomEncryptedNumber2, address(this)); + + TFHE.allow(randomEncryptedNumber, msg.sender); + TFHE.allow(randomEncryptedNumber1, msg.sender); + TFHE.allow(randomEncryptedNumber2, msg.sender); + } + + // Function to view encrypted random numbers + function viewEncryptedRandomNumber() public view returns (euint8, euint8, euint8) { + return (randomEncryptedNumber, randomEncryptedNumber1, randomEncryptedNumber2); + } +} diff --git a/contracts/PrivateVoting.sol b/contracts/PrivateVoting.sol new file mode 100644 index 0000000..e1639a9 --- /dev/null +++ b/contracts/PrivateVoting.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity >=0.8.13 <0.9.0; + +import "fhevm/lib/TFHE.sol"; +import "fhevm/gateway/GatewayCaller.sol"; + +contract Voting is GatewayCaller { + // Mappings for storing encrypted vote counts, choices, and voting status + mapping(address => euint64) internal encryptedVoteCounts; + mapping(address => ebool) internal encryptedVoteChoices; + mapping(address => bool) internal hasVoted; + + // Encrypted tallies for "in favor" and "against" votes + euint64 private inFavorCountEncrypted; + euint64 private againstCountEncrypted; + + // Owner and plaintext tallies for revealed results + address public owner; + uint64 public inFavorCount; + uint64 public againstCount; + + // Constructor sets up initial values and permissions for vote tallies + constructor() { + inFavorCountEncrypted = TFHE.asEuint64(0); + againstCountEncrypted = TFHE.asEuint64(0); + + // Allow the contract to manage these encrypted counts + TFHE.allow(inFavorCountEncrypted, address(this)); + TFHE.allow(againstCountEncrypted, address(this)); + + inFavorCount = 0; + againstCount = 0; + owner = msg.sender; + } + + // Modifier to restrict functions to the contract owner + modifier onlyOwner() { + require(msg.sender == owner, "Only Owner"); + _; + } + + /** + * @notice Casts a vote with a given encrypted vote count and choice + * @param encryptedVoteCount The encrypted voting power of the user + * @param encryptedChoice Encrypted choice where 0 = against, 1 = in favor + * @param inputProof Proof for decrypting inputs + */ + function castEncryptedVote(einput encryptedVoteCount, einput encryptedChoice, bytes calldata inputProof) public { + // Form the encrypted choice (0 or 1) for vote type + euint8 userChoice = TFHE.asEuint8(encryptedChoice, inputProof); + + // Determine the vote type as a boolean: true = in favor, false = against + ebool voteChoice = TFHE.eq(TFHE.asEuint8(1), userChoice); + + // Form the vote power + euint64 votePower = TFHE.asEuint64(encryptedVoteCount, inputProof); + + // Update or initialize the encrypted vote count for the sender + if (TFHE.isInitialized(encryptedVoteCounts[msg.sender])) { + TFHE.allow(encryptedVoteCounts[msg.sender], address(this)); + encryptedVoteCounts[msg.sender] = TFHE.add(encryptedVoteCounts[msg.sender], votePower); + } else { + encryptedVoteCounts[msg.sender] = votePower; + } + + // If already initialized, allow updating the global state variable and assign value; otherwise, assign directly + if (TFHE.isInitialized(encryptedVoteChoices[msg.sender])) { + TFHE.allow(encryptedVoteCounts[msg.sender], address(this)); + encryptedVoteChoices[msg.sender] = voteChoice; + } else { + encryptedVoteChoices[msg.sender] = voteChoice; + } + + // Initialize local variables to avoid issues with uninitialized handles + euint64 inFavorCountToCast = TFHE.asEuint64(0); + euint64 againstCountToCast = TFHE.asEuint64(0); + + // Conditional assignment based on vote choice + inFavorCountToCast = TFHE.select( + voteChoice, + votePower, // Set vote power for in-favor count if vote choice is true + TFHE.asEuint64(0) // Otherwise, set to 0 + ); + + againstCountToCast = TFHE.select( + voteChoice, + TFHE.asEuint64(0), // Set to 0 if vote choice is true + votePower // Set vote power for against count if vote choice is false + ); + + // Add the computed vote powers to the encrypted tallies + againstCountEncrypted = TFHE.add(againstCountEncrypted, againstCountToCast); + inFavorCountEncrypted = TFHE.add(inFavorCountEncrypted, inFavorCountToCast); + + // Allow both the contract and owner to access these encrypted tallies in the future + TFHE.allow(againstCountEncrypted, address(this)); + TFHE.allow(againstCountEncrypted, owner); + TFHE.allow(inFavorCountEncrypted, address(this)); + TFHE.allow(inFavorCountEncrypted, owner); + + // Allow the user to view their vote choice and vote count in the future + TFHE.allow(encryptedVoteCounts[msg.sender], msg.sender); + TFHE.allow(encryptedVoteChoices[msg.sender], msg.sender); + } + + /** + * @notice Allows the owner to reveal the final result by decrypting tallies + */ + function revealVotingResults() public onlyOwner { + // Retrieve encrypted tallies + euint64 totalInFavorCount = inFavorCountEncrypted; + euint64 totalAgainstCount = againstCountEncrypted; + + // Grant the contract permission to handle encrypted tallies + TFHE.allow(totalInFavorCount, address(this)); + TFHE.allow(totalAgainstCount, address(this)); + + // Request decryption of the final vote tallies + uint256[] memory cts = new uint256[](2); + cts[0] = Gateway.toUint256(totalInFavorCount); + cts[1] = Gateway.toUint256(totalAgainstCount); + + Gateway.requestDecryption(cts, this.decryptionCallback.selector, 0, block.timestamp + 100, false); + } + + /** + * @notice Callback function to handle decrypted results from the gateway + * @param totalFavourCountDecrypted Decrypted in-favor vote count + * @param totalAgainstCountDecrypted Decrypted against vote count + * @return True if the callback is successful + */ + function decryptionCallback( + uint256 /*requestID*/, + uint64 totalFavourCountDecrypted, + uint64 totalAgainstCountDecrypted + ) public onlyGateway returns (bool) { + // Update plaintext tallies with decrypted values + inFavorCount = totalFavourCountDecrypted; + againstCount = totalAgainstCountDecrypted; + return true; + } + + /** + * @notice Allows a user to view their own encrypted vote count + * @return The encrypted vote count of the sender + */ + function getOwnEncryptedVoteCount() public view returns (euint64) { + return encryptedVoteCounts[msg.sender]; + } + + /** + * @notice Allows a user to view their own encrypted vote choice + * @return The encrypted vote choice of the sender + */ + function getOwnEncryptedVoteChoice() public view returns (ebool) { + return encryptedVoteChoices[msg.sender]; + } + + /** + * @notice View the total encrypted count of in-favor votes + * @return The encrypted in-favor vote count + */ + function getEncryptedInFavorVoteCount() public view returns (euint64) { + return inFavorCountEncrypted; + } + + /** + * @notice View the total encrypted count of against votes + * @return The encrypted against vote count + */ + function getEncryptedAgainstVoteCount() public view returns (euint64) { + return againstCountEncrypted; + } +} diff --git a/contracts/TOTP.sol b/contracts/TOTP.sol new file mode 100644 index 0000000..d3e3ab1 --- /dev/null +++ b/contracts/TOTP.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity >=0.8.13 <0.9.0; +import "fhevm/lib/TFHE.sol"; + +contract TOTP { + // 4 digits + euint16 public secretKey; + address public owner; + mapping(address => ebool) public isTotpValid; + + constructor() { + owner = msg.sender; + } + + modifier OnlyOwner() { + require(msg.sender == owner, "Only owner"); + _; + } + + function setSecretKey(einput secretKeyInput, bytes calldata inputProof) public OnlyOwner { + secretKey = TFHE.asEuint16(secretKeyInput, inputProof); + TFHE.allow(secretKey, address(this)); + TFHE.allow(secretKey, owner); + } + + function validateTOTP(einput _encryptedTOTP, bytes memory inputProof, uint32 timestamp) external { + require(block.timestamp <= timestamp + 200, "Timestamp not within range"); + uint32 shorterTimestamp = timestamp % 100000; + euint32 encryptedTOTP = TFHE.asEuint32(_encryptedTOTP, inputProof); + ebool isValid = TFHE.eq(encryptedTOTP, TFHE.mul(TFHE.asEuint32(shorterTimestamp), secretKey)); + isTotpValid[msg.sender] = isValid; + TFHE.allow(isValid, address(this)); + TFHE.allow(isValid, msg.sender); + } + + function viewSecretKey() external view returns (euint16) { + return secretKey; + } + function getIsTotpValid(address _userAddress) external view returns(ebool){ + return isTotpValid[_userAddress]; + } +} diff --git a/test/BlindAuctionTests/BlindAuction.fixture.ts b/test/BlindAuctionTests/BlindAuction.fixture.ts new file mode 100644 index 0000000..3b097f5 --- /dev/null +++ b/test/BlindAuctionTests/BlindAuction.fixture.ts @@ -0,0 +1,18 @@ +import { AddressLike, BigNumberish, Signer } from 'ethers'; +import { ethers } from 'hardhat'; + +import type { BlindAuction } from '../../types'; + +export async function deployBlindAuctionFixture( + account: Signer, + tokenContract: AddressLike, + biddingTime: BigNumberish, + isStoppable: boolean, +): Promise { + const contractFactory = await ethers.getContractFactory('BlindAuction'); + const contract = await contractFactory + .connect(account) + .deploy(account.getAddress(), tokenContract, biddingTime, isStoppable); + await contract.waitForDeployment(); + return contract; +} \ No newline at end of file diff --git a/test/BlindAuctionTests/BlindAuction.ts b/test/BlindAuctionTests/BlindAuction.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/ConfidentialERC20Tests/ConfidentialERC20.ts b/test/ConfidentialERC20Tests/ConfidentialERC20.ts index 70d5510..be0ef9e 100644 --- a/test/ConfidentialERC20Tests/ConfidentialERC20.ts +++ b/test/ConfidentialERC20Tests/ConfidentialERC20.ts @@ -1,210 +1,210 @@ -import { expect } from "chai"; - -import { awaitAllDecryptionResults } from "../asyncDecrypt"; -import { createInstances } from "../instance"; -import { getSigners, initSigners } from "../signers"; -import { deployConfidentialERC20Fixture } from "./confidentialerc20.fixture"; - -describe("Confidential ERC20 tests", function () { - before(async function () { - await initSigners(); - this.signers = await getSigners(); - }); - - beforeEach(async function () { - const contract = await deployConfidentialERC20Fixture(); - this.contractAddress = await contract.getAddress(); - this.erc20 = contract; - this.instances = await createInstances(this.signers); - }); - - it("should not transfer tokens between two users", async function () { - const transaction = await this.erc20.mint(1000); - await transaction.wait(); - - const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - input.add64(1337); - const encryptedTransferAmount = 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.address); - const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); - const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); - const signatureAlice = await this.signers.alice.signTypedData( - eip712.domain, - { Reencrypt: eip712.types.Reencrypt }, - eip712.message, - ); - const balanceAlice = await this.instances.alice.reencrypt( - balanceHandleAlice, - privateKeyAlice, - publicKeyAlice, - signatureAlice.replace("0x", ""), - this.contractAddress, - this.signers.alice.address, - ); - - expect(balanceAlice).to.equal(1000); - - // Reencrypt Bob's balance - const balanceHandleBob = await this.erc20.balanceOf(this.signers.bob.address); - - const { publicKey: publicKeyBob, privateKey: privateKeyBob } = this.instances.bob.generateKeypair(); - const eip712Bob = this.instances.bob.createEIP712(publicKeyBob, this.contractAddress); - const signatureBob = await this.signers.bob.signTypedData( - eip712Bob.domain, - { Reencrypt: eip712Bob.types.Reencrypt }, - eip712Bob.message, - ); - const balanceBob = await this.instances.bob.reencrypt( - balanceHandleBob, - privateKeyBob, - publicKeyBob, - signatureBob.replace("0x", ""), - this.contractAddress, - this.signers.bob.address, - ); - - expect(balanceBob).to.equal(0); - }); - - it("should transfer tokens between two users", async function () { - const transaction = await this.erc20.mint(10000); - const t1 = await transaction.wait(); - expect(t1?.status).to.eq(1); - - const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - input.add64(1337); - const encryptedTransferAmount = input.encrypt(); - const tx = await this.erc20["transfer(address,bytes32,bytes)"]( - this.signers.bob.address, - encryptedTransferAmount.handles[0], - encryptedTransferAmount.inputProof, - ); - const t2 = await tx.wait(); - expect(t2?.status).to.eq(1); - - // Reencrypt Alice's balance - const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice); - const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); - const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); - const signatureAlice = await this.signers.alice.signTypedData( - eip712.domain, - { Reencrypt: eip712.types.Reencrypt }, - eip712.message, - ); - const balanceAlice = await this.instances.alice.reencrypt( - balanceHandleAlice, - privateKeyAlice, - publicKeyAlice, - signatureAlice.replace("0x", ""), - this.contractAddress, - this.signers.alice.address, - ); - - expect(balanceAlice).to.equal(10000 - 1337); - - // Reencrypt Bob's balance - const balanceHandleBob = await this.erc20.balanceOf(this.signers.bob); - - const { publicKey: publicKeyBob, privateKey: privateKeyBob } = this.instances.bob.generateKeypair(); - const eip712Bob = this.instances.bob.createEIP712(publicKeyBob, this.contractAddress); - const signatureBob = await this.signers.bob.signTypedData( - eip712Bob.domain, - { Reencrypt: eip712Bob.types.Reencrypt }, - eip712Bob.message, - ); - const balanceBob = await this.instances.bob.reencrypt( - balanceHandleBob, - privateKeyBob, - publicKeyBob, - signatureBob.replace("0x", ""), - this.contractAddress, - this.signers.bob.address, - ); - - expect(balanceBob).to.equal(1337); - - // on the other hand, Bob should be unable to read Alice's balance - try { - await this.instances.bob.reencrypt( - balanceHandleAlice, - privateKeyBob, - publicKeyBob, - signatureBob.replace("0x", ""), - this.contractAddress, - this.signers.bob.address, - ); - return expect.fail("Expected an error to be thrown - Bob should not be able to reencrypt Alice balance"); - } catch (error: unknown) { - expect((error as Error).message).to.equal("User is not authorized to reencrypt this handle!"); - } - }); - - - it("should mint to alice", async function () { - const transaction = await this.erc20.mint(1000); - - await transaction.wait(); - - //Reencrypt Alice's balance - const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice.address); - const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); - const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); - const signatureAlice = await this.signers.alice.signTypedData( - eip712.domain, - { Reencrypt: eip712.types.Reencrypt }, - eip712.message, - ); - const balanceAlice = await this.instances.alice.reencrypt( - balanceHandleAlice, - privateKeyAlice, - publicKeyAlice, - signatureAlice.replace("0x", ""), - this.contractAddress, - this.signers.alice.address, - ); - expect(balanceAlice).to.equal(1000); - - const totalSupply = await this.erc20._totalSupply(); - expect(totalSupply).to.equal(1000); - }); - - it("should mint tokens to Alice and decrypt her balance successfully", async function () { - const transaction = await this.erc20.mint(1000); - - await transaction.wait(); - - //Reencrypt Alice's balance - const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice.address); - const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); - const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); - const signatureAlice = await this.signers.alice.signTypedData( - eip712.domain, - { Reencrypt: eip712.types.Reencrypt }, - eip712.message, - ); - const balanceAlice = await this.instances.alice.reencrypt( - balanceHandleAlice, - privateKeyAlice, - publicKeyAlice, - signatureAlice.replace("0x", ""), - this.contractAddress, - this.signers.alice.address, - ); - expect(balanceAlice).to.equal(1000); - - const totalSupply = await this.erc20._totalSupply(); - expect(totalSupply).to.equal(1000); - - const decryptionTx = await this.erc20.requestUserBalanceDecryption(this.signers.alice.address); - await decryptionTx.wait(1); - await awaitAllDecryptionResults(); - }); - -}); +// import { expect } from "chai"; + +// import { awaitAllDecryptionResults } from "../asyncDecrypt"; +// import { createInstances } from "../instance"; +// import { getSigners, initSigners } from "../signers"; +// import { deployConfidentialERC20Fixture } from "./confidentialerc20.fixture"; + +// describe("Confidential ERC20 tests", function () { +// before(async function () { +// await initSigners(); +// this.signers = await getSigners(); +// }); + +// beforeEach(async function () { +// const contract = await deployConfidentialERC20Fixture(); +// this.contractAddress = await contract.getAddress(); +// this.erc20 = contract; +// this.instances = await createInstances(this.signers); +// }); + +// it("should not transfer tokens between two users", async function () { +// const transaction = await this.erc20.mint(1000); +// await transaction.wait(); + +// const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); +// input.add64(1337); +// const encryptedTransferAmount = 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.address); +// const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); +// const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); +// const signatureAlice = await this.signers.alice.signTypedData( +// eip712.domain, +// { Reencrypt: eip712.types.Reencrypt }, +// eip712.message, +// ); +// const balanceAlice = await this.instances.alice.reencrypt( +// balanceHandleAlice, +// privateKeyAlice, +// publicKeyAlice, +// signatureAlice.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); + +// expect(balanceAlice).to.equal(1000); + +// // Reencrypt Bob's balance +// const balanceHandleBob = await this.erc20.balanceOf(this.signers.bob.address); + +// const { publicKey: publicKeyBob, privateKey: privateKeyBob } = this.instances.bob.generateKeypair(); +// const eip712Bob = this.instances.bob.createEIP712(publicKeyBob, this.contractAddress); +// const signatureBob = await this.signers.bob.signTypedData( +// eip712Bob.domain, +// { Reencrypt: eip712Bob.types.Reencrypt }, +// eip712Bob.message, +// ); +// const balanceBob = await this.instances.bob.reencrypt( +// balanceHandleBob, +// privateKeyBob, +// publicKeyBob, +// signatureBob.replace("0x", ""), +// this.contractAddress, +// this.signers.bob.address, +// ); + +// expect(balanceBob).to.equal(0); +// }); + +// it("should transfer tokens between two users", async function () { +// const transaction = await this.erc20.mint(10000); +// const t1 = await transaction.wait(); +// expect(t1?.status).to.eq(1); + +// const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); +// input.add64(1337); +// const encryptedTransferAmount = input.encrypt(); +// const tx = await this.erc20["transfer(address,bytes32,bytes)"]( +// this.signers.bob.address, +// encryptedTransferAmount.handles[0], +// encryptedTransferAmount.inputProof, +// ); +// const t2 = await tx.wait(); +// expect(t2?.status).to.eq(1); + +// // Reencrypt Alice's balance +// const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice); +// const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); +// const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); +// const signatureAlice = await this.signers.alice.signTypedData( +// eip712.domain, +// { Reencrypt: eip712.types.Reencrypt }, +// eip712.message, +// ); +// const balanceAlice = await this.instances.alice.reencrypt( +// balanceHandleAlice, +// privateKeyAlice, +// publicKeyAlice, +// signatureAlice.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); + +// expect(balanceAlice).to.equal(10000 - 1337); + +// // Reencrypt Bob's balance +// const balanceHandleBob = await this.erc20.balanceOf(this.signers.bob); + +// const { publicKey: publicKeyBob, privateKey: privateKeyBob } = this.instances.bob.generateKeypair(); +// const eip712Bob = this.instances.bob.createEIP712(publicKeyBob, this.contractAddress); +// const signatureBob = await this.signers.bob.signTypedData( +// eip712Bob.domain, +// { Reencrypt: eip712Bob.types.Reencrypt }, +// eip712Bob.message, +// ); +// const balanceBob = await this.instances.bob.reencrypt( +// balanceHandleBob, +// privateKeyBob, +// publicKeyBob, +// signatureBob.replace("0x", ""), +// this.contractAddress, +// this.signers.bob.address, +// ); + +// expect(balanceBob).to.equal(1337); + +// // on the other hand, Bob should be unable to read Alice's balance +// try { +// await this.instances.bob.reencrypt( +// balanceHandleAlice, +// privateKeyBob, +// publicKeyBob, +// signatureBob.replace("0x", ""), +// this.contractAddress, +// this.signers.bob.address, +// ); +// return expect.fail("Expected an error to be thrown - Bob should not be able to reencrypt Alice balance"); +// } catch (error: unknown) { +// expect((error as Error).message).to.equal("User is not authorized to reencrypt this handle!"); +// } +// }); + + +// it("should mint to alice", async function () { +// const transaction = await this.erc20.mint(1000); + +// await transaction.wait(); + +// //Reencrypt Alice's balance +// const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice.address); +// const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); +// const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); +// const signatureAlice = await this.signers.alice.signTypedData( +// eip712.domain, +// { Reencrypt: eip712.types.Reencrypt }, +// eip712.message, +// ); +// const balanceAlice = await this.instances.alice.reencrypt( +// balanceHandleAlice, +// privateKeyAlice, +// publicKeyAlice, +// signatureAlice.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); +// expect(balanceAlice).to.equal(1000); + +// const totalSupply = await this.erc20._totalSupply(); +// expect(totalSupply).to.equal(1000); +// }); + +// it("should mint tokens to Alice and decrypt her balance successfully", async function () { +// const transaction = await this.erc20.mint(1000); + +// await transaction.wait(); + +// //Reencrypt Alice's balance +// const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice.address); +// const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); +// const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); +// const signatureAlice = await this.signers.alice.signTypedData( +// eip712.domain, +// { Reencrypt: eip712.types.Reencrypt }, +// eip712.message, +// ); +// const balanceAlice = await this.instances.alice.reencrypt( +// balanceHandleAlice, +// privateKeyAlice, +// publicKeyAlice, +// signatureAlice.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); +// expect(balanceAlice).to.equal(1000); + +// const totalSupply = await this.erc20._totalSupply(); +// expect(totalSupply).to.equal(1000); + +// const decryptionTx = await this.erc20.requestUserBalanceDecryption(this.signers.alice.address); +// await decryptionTx.wait(1); +// await awaitAllDecryptionResults(); +// }); + +// }); diff --git a/test/ConfidentialERC20Tests/confidentialerc20.fixture.ts b/test/ConfidentialERC20Tests/confidentialerc20.fixture.ts index f4c37a9..5f56bfa 100644 --- a/test/ConfidentialERC20Tests/confidentialerc20.fixture.ts +++ b/test/ConfidentialERC20Tests/confidentialerc20.fixture.ts @@ -3,7 +3,7 @@ import { ethers } from "hardhat"; import type { ConfidentialERC20 } from "../../types"; import { getSigners } from "../signers"; -export async function deployConfidentialERC20Fixture(): Promise { +export async function deployConfidentialERC20Fixture (): Promise { const signers = await getSigners(); const contractFactory = await ethers.getContractFactory("ConfidentialERC20"); diff --git a/test/DecryptionExample/DecryptionExample.fixture.ts b/test/DecryptionExample/DecryptionExample.fixture.ts new file mode 100644 index 0000000..02e204d --- /dev/null +++ b/test/DecryptionExample/DecryptionExample.fixture.ts @@ -0,0 +1,16 @@ + +import { ethers } from "hardhat"; + +import type { DecryptionExample } from "../../types"; +import { getSigners } from "../signers"; + +export async function deployDecryptiponExampleFixture(): Promise { + const signers = await getSigners(); + + const contractFactory = await ethers.getContractFactory("DecryptionExample"); + const contract = await contractFactory.connect(signers.alice).deploy(); + await contract.waitForDeployment(); + console.log("DecryptionExample Contract Address is:", await contract.getAddress()); + + return contract; +} diff --git a/test/DecryptionExample/DecryptionExample.ts b/test/DecryptionExample/DecryptionExample.ts new file mode 100644 index 0000000..3729114 --- /dev/null +++ b/test/DecryptionExample/DecryptionExample.ts @@ -0,0 +1,41 @@ +import { expect } from "chai"; + +import { awaitAllDecryptionResults } from "../asyncDecrypt"; +import { createInstances } from "../instance"; +import { getSigners, initSigners } from "../signers"; +import { deployDecryptiponExampleFixture } from "./DecryptionExample.fixture"; + +describe("Decryption Example Tests", function () { + before(async function () { + // Initialize signers before running tests + await initSigners(); + this.signers = await getSigners(); + }); + + beforeEach(async function () { + // Deploy the Random Number Generator contract before each test + const dceContract = await deployDecryptiponExampleFixture(); + this.contractAddress = await dceContract.getAddress(); + this.dceContract = dceContract; + this.instances = await createInstances(this.signers); + }); + + it("Should generate random numbers, re-encrypt, and retrieve them", async function () { + // Generate random numbers + const transaction = await this.dceContract.generateRandomNumber({ gasLimit: 5000000 }); + await transaction.wait(); + + await awaitAllDecryptionResults(); + + // Wait for 5 seconds + await delay(10000); + + // Output the re-encrypted random numbers + console.log("First Random Number:", Number(await this.dceContract.randomNumber()) % 53); + console.log("Second Random Number:", Number(await this.dceContract.randomNumber1()) % 53); + }); + + function delay(ms: any) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +}); diff --git a/test/HiddenRandomCardsTests/HiddenRandomCards.fixture.ts b/test/HiddenRandomCardsTests/HiddenRandomCards.fixture.ts new file mode 100644 index 0000000..468359e --- /dev/null +++ b/test/HiddenRandomCardsTests/HiddenRandomCards.fixture.ts @@ -0,0 +1,16 @@ + +import { ethers } from "hardhat"; + +import type { HiddenRandomCards } from "../../types"; +import { getSigners } from "../signers"; + +export async function deployRandomNumberGeneratorFixture(): Promise { + const signers = await getSigners(); + + const contractFactory = await ethers.getContractFactory("HiddenRandomCards"); + const contract = await contractFactory.connect(signers.alice).deploy(); + await contract.waitForDeployment(); + console.log("HiddenRandomCards Contract Address is:", await contract.getAddress()); + + return contract; +} diff --git a/test/HiddenRandomCardsTests/HiddenRandomCards.ts b/test/HiddenRandomCardsTests/HiddenRandomCards.ts new file mode 100644 index 0000000..16f5316 --- /dev/null +++ b/test/HiddenRandomCardsTests/HiddenRandomCards.ts @@ -0,0 +1,75 @@ +// import { expect } from "chai"; +// import { awaitAllDecryptionResults } from "../asyncDecrypt"; +// import { createInstances } from "../instance"; +// import { getSigners, initSigners } from "../signers"; +// import { deployRandomNumberGeneratorFixture } from "./HiddenRandomCards.fixture"; + +// describe("Random Number Generator Tests", function () { +// before(async function () { +// // Initialize signers before running tests +// await initSigners(); +// this.signers = await getSigners(); +// }); + +// beforeEach(async function () { +// // Deploy the Random Number Generator contract before each test +// const rngContract = await deployRandomNumberGeneratorFixture(); +// this.contractAddress = await rngContract.getAddress(); +// this.rngContract = rngContract; +// this.instances = await createInstances(this.signers); +// }); + +// it("Should generate random numbers, re-encrypt, and retrieve them", async function () { +// // Generate random numbers +// const transaction = await this.rngContract.generateRandomNumber({ gasLimit: 5000000 }); +// await transaction.wait(); + +// // Retrieve encrypted random numbers from the contract +// const [encRandomNum1, encRandomNum2, encRandomNum3] = await this.rngContract.viewEncryptedRandomNumber(); + +// // Generate public-private keypair for Alice +// const { publicKey: alicePublicKey, privateKey: alicePrivateKey } = this.instances.alice.generateKeypair(); + +// // Prepare EIP-712 signature for Alice's re-encryption request +// const eip712Message = this.instances.alice.createEIP712(alicePublicKey, this.contractAddress); +// const aliceSignature = await this.signers.alice.signTypedData( +// eip712Message.domain, +// { Reencrypt: eip712Message.types.Reencrypt }, +// eip712Message.message, +// ); + +// // Re-encrypt each random number and retrieve results +// const firstRandomNumber = await this.instances.alice.reencrypt( +// encRandomNum1, +// alicePrivateKey, +// alicePublicKey, +// aliceSignature.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); + +// const secondRandomNumber = await this.instances.alice.reencrypt( +// encRandomNum2, +// alicePrivateKey, +// alicePublicKey, +// aliceSignature.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); + +// const thirdRandomNumber = await this.instances.alice.reencrypt( +// encRandomNum3, +// alicePrivateKey, +// alicePublicKey, +// aliceSignature.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); + +// // Output the re-encrypted random numbers +// console.log("First Random Number:", Number(firstRandomNumber)%53); +// console.log("Second Random Number:", Number(secondRandomNumber)%53); +// console.log("Third Random Number:", Number(thirdRandomNumber)%53); +// }); + +// }); diff --git a/test/TOTPTests/TOTP.fixture.ts b/test/TOTPTests/TOTP.fixture.ts new file mode 100644 index 0000000..b402617 --- /dev/null +++ b/test/TOTPTests/TOTP.fixture.ts @@ -0,0 +1,16 @@ +import { ethers } from "hardhat"; + +import type { TOTP } from "../../types"; +import { getSigners } from "../signers"; + +export async function deployTOTPFixture (): Promise { + const signers = await getSigners(); + + const contractFactory = await ethers.getContractFactory("TOTP"); + const contract = await contractFactory.connect(signers.alice).deploy(); + await contract.waitForDeployment(); + + console.log("TOTP Contract Address is:", await contract.getAddress()); + + return contract; +} diff --git a/test/TOTPTests/TOTP.ts b/test/TOTPTests/TOTP.ts new file mode 100644 index 0000000..3f21934 --- /dev/null +++ b/test/TOTPTests/TOTP.ts @@ -0,0 +1,93 @@ +// import { expect } from "chai"; + +// import { awaitAllDecryptionResults } from "../asyncDecrypt"; +// import { createInstances } from "../instance"; +// import { getSigners, initSigners } from "../signers"; +// import { deployTOTPFixture } from "./TOTP.fixture"; + +// describe("Random Number Generator Tests", function () { +// before(async function () { +// // Initialize signers before running tests +// await initSigners(); +// this.signers = await getSigners(); +// }); + +// beforeEach(async function () { +// // Deploy the Random Number Generator contract before each test +// const contract = await deployTOTPFixture(); +// this.contractAddress = await contract.getAddress(); +// this.totp = contract; +// this.instances = await createInstances(this.signers); +// }); + +// describe("TOTP Setup", function () { +// it("Should be able to validate the OTP", async function () { +// const inputForSecretKey = this.instances.alice.createEncryptedInput( +// this.contractAddress, +// this.signers.alice.address, +// ); +// inputForSecretKey.add16(1000); // Ensure 'totp' is a BigInt for add32 +// const encryptedSecretKey = inputForSecretKey.encrypt(); +// const setTotpSecretKey = await this.totp.setSecretKey( +// encryptedSecretKey.handles[0], +// encryptedSecretKey.inputProof, +// ); +// await setTotpSecretKey.wait(); +// // Get the TOTP secret key first +// const secretKeyHandle = await this.totp.viewSecretKey(); +// const { publicKey: publicKeyAlice, privateKey: privateKeyAlice } = this.instances.alice.generateKeypair(); +// const eip712 = this.instances.alice.createEIP712(publicKeyAlice, this.contractAddress); +// const signatureAlice = await this.signers.alice.signTypedData( +// eip712.domain, +// { Reencrypt: eip712.types.Reencrypt }, +// eip712.message, +// ); +// const secretKey = await this.instances.alice.reencrypt( +// secretKeyHandle, +// privateKeyAlice, +// publicKeyAlice, +// signatureAlice.replace("0x", ""), +// this.contractAddress, +// this.signers.alice.address, +// ); + +// const currentTimestamp = Math.floor(Date.now() / 1000); +// const last5TimeStamp = currentTimestamp % 100000; + +// // Ensure secretKey is of type number for multiplication +// const totp = last5TimeStamp * Number(secretKey); + +// // Now we have access to the TOTP value, use Bob to validate the TOTP +// const input = this.instances.bob.createEncryptedInput(this.contractAddress, this.signers.bob.address); +// input.add32(BigInt(totp)); // Ensure 'totp' is a BigInt for add32 +// const encryptedTotp = input.encrypt(); + +// const validateTx = await this.totp +// .connect(this.signers.bob) +// .validateTOTP(encryptedTotp.handles[0], encryptedTotp.inputProof, currentTimestamp); + +// await validateTx.wait(); + +// const resultHandle = await this.totp.getIsTotpValid(this.signers.bob.address); + +// const { publicKey: publicKeyBob, privateKey: privateKeyBob } = this.instances.bob.generateKeypair(); +// const eip712ForBob = this.instances.bob.createEIP712(publicKeyBob, this.contractAddress); +// const signatureBob = await this.signers.bob.signTypedData( +// eip712ForBob.domain, +// { Reencrypt: eip712ForBob.types.Reencrypt }, +// eip712ForBob.message, +// ); + +// const actualValue = await this.instances.bob.reencrypt( +// resultHandle, +// privateKeyBob, +// publicKeyBob, +// signatureBob.replace("0x", ""), +// this.contractAddress, +// this.signers.bob.address, +// ); + +// console.log("The actual value is:", actualValue); +// }); +// }); +// });