Skip to content
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
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
node_modules
.env

# Hardhat files
/cache
/artifacts

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337
79 changes: 79 additions & 0 deletions contracts/EncryptedERC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.27;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {TokenTracker} from "./TokenTracker.sol";
import {EncryptedUserBalances} from "./EncryptedUserBalances.sol";

import {IRegistrar} from "./interfaces/IRegistrar.sol";
import {CreateEncryptedERCParams, Point} from "./types/Types.sol";
import {UserNotRegistered} from "./errors/Errors.sol";

contract EncryptedERC is TokenTracker, Ownable, EncryptedUserBalances {
// registrar contract
IRegistrar public registrar;

// token name and symbol
string public name;
string public symbol;

// 2 decimal places
uint256 public constant decimals = 2;

// auditor
Point public auditorPublicKey;
address public auditor;

constructor(
CreateEncryptedERCParams memory params
) TokenTracker(params._isConverter) Ownable(msg.sender) {
registrar = IRegistrar(params._registrar);

// if contract is not a converter, then set the name and symbol
if (!params._isConverter) {
name = params._name;
symbol = params._symbol;
}
}

///////////////////////////////////////////////////
/// Events ///
///////////////////////////////////////////////////

/**
* @param oldAuditor Address of the old auditor
* @param newAuditor Address of the new auditor
* @dev Emitted when the auditor public key is changed
*/
event AuditorChanged(
address indexed oldAuditor,
address indexed newAuditor
);

/**
*
* @param _user Address of the user
*
* @dev sets the auditor public key
*/
function setAuditorPublicKey(address _user) external onlyOwner {
if (!registrar.isUserRegistered(_user)) {
revert UserNotRegistered();
}

address oldAuditor = auditor;
uint256[2] memory publicKey = registrar.getUserPublicKey(_user);

auditor = _user;
auditorPublicKey = Point({X: publicKey[0], Y: publicKey[1]});

emit AuditorChanged(oldAuditor, _user);
}

/**
* @return bool returns true if the auditor public key is set
*/
function isAuditorKeySet() public view returns (bool) {
return auditorPublicKey.X != 0 && auditorPublicKey.Y != 0;
}
}
130 changes: 130 additions & 0 deletions contracts/EncryptedUserBalances.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.27;

import {EncryptedBalance, EGCT, BalanceHistory} from "./types/Types.sol";

contract EncryptedUserBalances {
mapping(address user => mapping(uint256 tokenId => EncryptedBalance balance))
public balances;

/**
*
* @param _user User address
* @return eGCT Elgamal Ciphertext
* @return nonce Nonce
* @dev Returns the balance of the user for the standalone token (tokenId = 0)
*/
function balanceOfForStandalone(
address _user
) external view returns (EGCT memory eGCT, uint256 nonce) {
return balanceOf(_user, 0);
}

/**
* @param _user User address
* @param _tokenId Token ID
* @return eGCT Elgamal Ciphertext
* @return nonce Nonce
* @dev Returns the balance of the user for the given token
*/
function balanceOf(
address _user,
uint256 _tokenId
) public view returns (EGCT memory eGCT, uint256 nonce) {
EncryptedBalance storage balance = balances[_user][_tokenId];
return (balance.eGCT, balance.nonce);
}

///////////////////////////////////////////////////
/// Internal Functions ///
///////////////////////////////////////////////////

/**
* @param _user User address
* @param _tokenId Token ID
* @dev Adds the balance hash to the user's history
* @dev Hash EGCT with the nonce and mark the result as valid
* every time user send a transaction nonce is increased by 1
* so the balance hash is unique for each transaction and sender must prove
* that the balance hash is known beforehand with the current nonce
*/
function _addToUserHistory(address _user, uint256 _tokenId) internal {
EncryptedBalance storage balance = balances[_user][_tokenId];

uint256 nonce = balance.nonce;
uint256 balanceHash = _hashEGCT(balance.eGCT);
balanceHash = uint256(keccak256(abi.encode(balanceHash, nonce)));

// mark the balance hash as valid
balance.balanceList[balanceHash] = BalanceHistory({
index: balance.nextBalanceIndex,
isValid: true
});

balance.nextBalanceIndex++;
}

/**
* @param _eGCT Elgamal Ciphertext
* @return hash of the Elgamal Ciphertext CRH(eGCT)
*/
function _hashEGCT(EGCT memory _eGCT) internal pure returns (uint256) {
return
uint256(
keccak256(
abi.encode(_eGCT.c1.X, _eGCT.c1.Y, _eGCT.c2.X, _eGCT.c2.Y)
)
);
}

/**
* @param _user User address
* @param _tokenId Token ID
* @dev Deletes the user's history
* @dev Instead of deleting the history mapping one by one, we can just
* increase the nonce by one and the old history will be mark as invalid
*/
function _deleteUserHistory(address _user, uint256 _tokenId) internal {
EncryptedBalance storage balance = balances[_user][_tokenId];

// before setting the next balnace index to 0, we need to clear the amount pcts
// from index 0 to balance.nextBalanceIndex
uint256 newLength = balance.amountPCTs.length -
balance.nextBalanceIndex;
for (uint256 i = 0; i < newLength; i++) {
balance.amountPCTs[i] = balance.amountPCTs[
i + balance.nextBalanceIndex
];
}
// Resize the array to remove excess elements
while (balance.amountPCTs.length > newLength) {
balance.amountPCTs.pop();
}

balance.nonce++;
// setting the next balance index to 0 to start over
balance.nextBalanceIndex = 0;

// and need to add the new balance hash to the history with the new nonce
_addToUserHistory(_user, _tokenId);
}

/**
* @param _user User address
* @param _tokenId Token ID
* @param _balanceHash Balance hash
* @return isValid True if the balance hash is valid
* @dev Hash the provided eGCT with the current nonce and check if it's in the history
*/
function _isBalanceValid(
address _user,
uint256 _tokenId,
uint256 _balanceHash
) internal view returns (bool) {
uint256 nonce = balances[_user][_tokenId].nonce;
uint256 hashWithNonce = uint256(
keccak256(abi.encode(_balanceHash, nonce))
);
return balances[_user][_tokenId].balanceList[hashWithNonce].isValid;
}
}
34 changes: 34 additions & 0 deletions contracts/Lock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

// Uncomment this line to use console.log
// import "hardhat/console.sol";

contract Lock {
uint public unlockTime;
address payable public owner;

event Withdrawal(uint amount, uint when);

constructor(uint _unlockTime) payable {
require(
block.timestamp < _unlockTime,
"Unlock time should be in the future"
);

unlockTime = _unlockTime;
owner = payable(msg.sender);
}

function withdraw() public {
// Uncomment this line, and the import of "hardhat/console.sol", to print a log in your terminal
// console.log("Unlock time is %o and block timestamp is %o", unlockTime, block.timestamp);

require(block.timestamp >= unlockTime, "You can't withdraw yet");
require(msg.sender == owner, "You aren't the owner");

emit Withdrawal(address(this).balance, block.timestamp);

owner.transfer(address(this).balance);
}
}
80 changes: 80 additions & 0 deletions contracts/Registrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.27;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Point} from "./types/Types.sol";
import {UserAlreadyRegistered} from "./errors/Errors.sol";

// import {Point, User, RegisterProof} from "./structs/Structs.sol";
// import {IRegisterVerifier} from "./interfaces/IRegisterVerifier.sol";
// import {DuplicatePublicKey, InvalidProof} from "./errors/Errors.sol";

contract Registrar {
address public constant BURN_USER =
0x1111111111111111111111111111111111111111;

/**
* @dev Mapping of user addresses to their public keys
*/
mapping(address userAddress => Point userPublicKey) public userPublicKeys;

constructor() {
// setting burn user to the identity point (0, 1)
userPublicKeys[BURN_USER] = Point({X: 0, Y: 1});
}

/**
*
* @param user Address of the user
* @param publicKey Public key of the user
*/
event Register(address indexed user, Point publicKey);

// TODO(@mberatoz): pass the proof as a parameter
function register() external {
address account = msg.sender;

// TODO(@mberatoz): verify the proof

if (isUserRegistered(account)) {
revert UserAlreadyRegistered();
}

// TODO(@mberatoz): change this to the actual public key from the public ins
_register(account, Point({X: 0, Y: 1}));
}

/**
*
* @param _user Address of the user
* @param _publicKey Public key of the user
*
* @dev Internal function for setting user public key
*/
function _register(address _user, Point memory _publicKey) internal {
userPublicKeys[_user] = _publicKey;
emit Register(_user, _publicKey);
}

/**
*
* @param _user Address of the user
*
* @return bool True if the user is registered
*/
function isUserRegistered(address _user) public view returns (bool) {
return userPublicKeys[_user].X != 0 && userPublicKeys[_user].Y != 0;
}

/**
*
* @param _user Address of the user
*
* @return publicKey Public key of the user as [x, y] coordinates
*/
function getUserPublicKey(
address _user
) public view returns (uint256[2] memory publicKey) {
return [userPublicKeys[_user].X, userPublicKeys[_user].Y];
}
}
34 changes: 34 additions & 0 deletions contracts/TokenTracker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.27;

contract TokenTracker {
// starting from 1 becase 0 is for standalone version of the EncryptedERC
uint256 public nextTokenId = 1;
// indicates if the contract is a converter
bool public isConverter;

mapping(address tokenAddress => uint256 tokenId) public tokenIds;

address[] public tokens;

constructor(bool _isConverter) {
isConverter = _isConverter;
}

/**
* @return Array of token addresses
*/
function getTokens() external view returns (address[] memory) {
return tokens;
}

/**
* @param _tokenAddress Address of the token
* @dev Adds a token to the tracker
*/
function _addToken(address _tokenAddress) internal {
tokenIds[_tokenAddress] = nextTokenId;
tokens.push(_tokenAddress);
nextTokenId++;
}
}
5 changes: 5 additions & 0 deletions contracts/errors/Errors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.27;

error UserAlreadyRegistered();
error UserNotRegistered();
Loading