Defining functions

  1. Constructor

    • Define a constructor such that whoever created the contract is the owner of the token.

    constructor() EIP712WithModifier("Authorization token", "1") {
            contractOwner = msg.sender;
        }
  2. Minting Tokens:

    • mint function to create new tokens, updating totalSupply and the owner's balance. We use TFHE operations to add tokens to the owner's balance. When user sends ciphertext as bytes, we need to convert it to the respective integer type to ensure it's well formed.

        function mint(bytes calldata encryptedAmount) public {
            euint32 amount = TFHE.asEuint32(encryptedAmount);
            balances[msg.sender] = TFHE.add(balances[msg.sender], amount);
            totalSupply = TFHE.add(totalSupply, amount);
        }
  3. Transferring Tokens:

    • transfer function that allows token holders to send tokens to another address. We use TFHE operations for encrypted checks (e.g., balance sufficiency) and updates. optReq is the equivalent of Solidity's require, but for TFHE data types.

        function _transfer(address from, address to, euint32 amount) internal {
            // Make sure the sender has enough tokens.
            // optReq is the equivalent of Solidity's require for TFHE data types.
            // It is optimistic, meaning that the transaction will revert only at the
            // end of its execution.
            TFHE.optReq(TFHE.le(amount, balances[from]));
    
            // Add to the balance of `to` and subract from the balance of `from`.
            balances[to] = TFHE.add(balances[to], amount);
            balances[from] = TFHE.sub(balances[from], amount);
        }
  4. Approving Tokens:

    • approve function enables token holders to set an allowance for another address.

        // Sets the `encryptedAmount` as the allowance of `spender` over the caller's tokens.
        function approve(address spender, bytes calldata encryptedAmount) public {
            address owner = msg.sender;
            _approve(owner, spender, TFHE.asEuint32(encryptedAmount));
        } 
        
        function _approve(address owner, address spender, euint32 amount) internal {
            allowances[owner][spender] = amount;
        }
  5. Transferring Tokens from Another Address:

    • transferFrom function should enable a spender to transfer tokens within the allowance set for them.

        // Transfers `encryptedAmount` tokens using the caller's allowance.
        function transferFrom(
            address from,
            address to,
            bytes calldata encryptedAmount
        ) public {
            transferFrom(from, to, TFHE.asEuint32(encryptedAmount));
        }
    
        // Transfers `amount` tokens using the caller's allowance.
        function transferFrom(address from, address to, euint32 amount) public {
            address spender = msg.sender;
            _updateAllowance(from, spender, amount);
            _transfer(from, to, amount);
        }
        
        function _updateAllowance(
            address owner,
            address spender,
            euint32 amount
        ) internal {
            euint32 currentAllowance = _allowance(owner, spender);
            TFHE.optReq(TFHE.le(amount, currentAllowance));
            _approve(owner, spender, TFHE.sub(currentAllowance, amount));
        }

Viewing encrypted balances and allowances:

  1. Balances:

    • We implement a function that allows the user to see their own encrypted balance. This is done by re-encrypting the balance with the user's public key. The user can then decrypt their own balance on the frontend using the associated private key.

      // Returns the balance of the caller under their public FHE key.
          // The FHE public key is automatically determined based on the origin of the call.
          function balanceOf(
              bytes32 publicKey,
              bytes calldata signature
          )
              public
              view
              onlySignedPublicKey(publicKey, signature)
              returns (bytes memory)
          {
              return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
          }
  2. Allowances:

    • Similarly, we write a function to view the encrypted allowance set for a spender by the contract creator, using re-encryption for confidentiality.

        // Returns the remaining number of tokens that `spender` is allowed to spend
        // on behalf of the caller. The returned ciphertext is under the caller public FHE key.
        function allowance(
            address spender,
            bytes32 publicKey,
            bytes calldata signature
        )
            public
            view
            onlySignedPublicKey(publicKey, signature)
            returns (bytes memory)
        {
            address owner = msg.sender;
    
            return TFHE.reencrypt(_allowance(owner, spender), publicKey);
        } 
  3. Total Supply:

    • We write a function getTotalSupply to allow only the contract creator to view the encrypted total supply using re-encryption.

    function getTotalSupply(
            bytes32 publicKey,
            bytes calldata signature
        )
            public
            view
            onlyContractOwner
            onlySignedPublicKey(publicKey, signature)
            returns (bytes memory)
        {
            return TFHE.reencrypt(totalSupply, publicKey);
        }

Last updated