githubEdit

Architecture

The diagram below shows how PrivateERC20 is composed. It inherits from standard OpenZeppelin contracts for access control and reentrancy protection, and delegates all encrypted arithmetic to the MPC precompile via the MpcCore library.

PrivateERC20 (abstract)
β”œβ”€β”€ inherits: Context, ERC165, IPrivateERC20, AccessControl, ReentrancyGuard
β”œβ”€β”€ storage:
β”‚   β”œβ”€β”€ _balances: mapping(address => utUint256)   // { ciphertext, userCiphertext }
β”‚   β”œβ”€β”€ _allowances: mapping(address => mapping(address => Allowance))
β”‚   β”œβ”€β”€ _totalSupply: ctUint256                    // always decrypts to real supply
β”‚   └── _accountEncryptionAddress: mapping(address => address)
β”œβ”€β”€ roles:
β”‚   β”œβ”€β”€ DEFAULT_ADMIN_ROLE  β†’ deployer
β”‚   └── MINTER_ROLE         β†’ bridges, vesting contracts, etc.
└── MpcCore (precompile at 0x64)
    β”œβ”€β”€ setPublic256(uint256) β†’ gtUint256
    β”œβ”€β”€ validateCiphertext(itUint256) β†’ gtUint256
    β”œβ”€β”€ transfer(gt, gt, gt) β†’ (gt, gt, gtBool)
    β”œβ”€β”€ offBoard(gtUint256) β†’ ctUint256
    β”œβ”€β”€ offBoardToUser(gtUint256, address) β†’ ctUint256
    └── onBoard(ctUint256) β†’ gtUint256

Key Concepts

Understanding COTI data types is essential before working with any PrivateERC20 function. Every encrypted operation moves values through these representations in a specific order: user input arrives as itUint256, gets loaded into gtUint256 for in-memory computation, and is stored back on-chain as ctUint256.

Term
Description

gtUint256

Garbled-text uint256 β€” an in-memory encrypted value used during MPC computation

ctUint256

Ciphertext uint256 β€” an encrypted value stored on-chain

itUint256

Input-text uint256 β€” an encrypted value submitted by a user (ciphertext + signature)

MpcCore

Solidity library that calls the COTI MPC precompile at address(0x64)

AES Key

A 16-byte (32 hex char) key derived per wallet during onboarding, used to decrypt balances

Last updated

Was this helpful?