├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── foundry.toml ├── script └── Firn.s.sol └── src ├── BalanceTree.sol ├── DepositVerifier.sol ├── ERC20.sol ├── EpochTree.sol ├── Firn.sol ├── FirnReader.sol ├── InnerProductVerifier.sol ├── TransferVerifier.sol ├── Treasury.sol ├── Utils.sol └── WithdrawalVerifier.sol /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | with: 26 | version: nightly 27 | 28 | - name: Show Forge version 29 | run: | 30 | forge --version 31 | 32 | - name: Run Forge fmt 33 | run: | 34 | forge fmt --check 35 | id: fmt 36 | 37 | - name: Run Forge build 38 | run: | 39 | forge build --sizes 40 | id: build 41 | 42 | - name: Run Forge tests 43 | run: | 44 | forge test -vvv 45 | id: test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | [fmt] 7 | line_length = 300 8 | 9 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 10 | -------------------------------------------------------------------------------- /script/Firn.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | import {InnerProductVerifier} from "../src/InnerProductVerifier.sol"; 6 | import {DepositVerifier} from "../src/DepositVerifier.sol"; 7 | import {TransferVerifier} from "../src/TransferVerifier.sol"; 8 | import {WithdrawalVerifier} from "../src/WithdrawalVerifier.sol"; 9 | import {ERC20} from "../src/ERC20.sol"; 10 | import {Treasury} from "../src/Treasury.sol"; 11 | import {Firn} from "../src/Firn.sol"; 12 | import {FirnReader} from "../src/FirnReader.sol"; 13 | 14 | contract FirnScript is Script { 15 | Firn public counter; 16 | 17 | function setUp() public {} 18 | 19 | function run() public { 20 | // uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 21 | vm.startBroadcast(); // deployerPrivateKey 22 | 23 | InnerProductVerifier ip = new InnerProductVerifier(); 24 | DepositVerifier deposit = new DepositVerifier(ip); 25 | TransferVerifier transfer = new TransferVerifier(ip); 26 | WithdrawalVerifier withdrawal = new WithdrawalVerifier(ip); 27 | 28 | ERC20 token = new ERC20("Firn Token", "FIRN"); 29 | Treasury treasury = new Treasury(token); 30 | 31 | Firn firn = new Firn(deposit, transfer, withdrawal, address(treasury)); 32 | FirnReader reader = new FirnReader(firn); 33 | 34 | vm.stopBroadcast(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/BalanceTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | // ---------------------------------------------------------------------------- 6 | // BokkyPooBah's Red-Black Tree Library v1.0-pre-release-a 7 | // 8 | // A Solidity Red-Black Tree binary search library to store and access a sorted 9 | // list of unsigned integer data. The Red-Black algorithm rebalances the binary 10 | // search tree, resulting in O(log n) insert, remove and search time (and ~gas) 11 | // 12 | // https://github.com/bokkypoobah/BokkyPooBahsRedBlackTreeLibrary 13 | // 14 | // 15 | // Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2020. The MIT Licence. 16 | // ---------------------------------------------------------------------------- 17 | abstract contract BalanceTree { 18 | struct Node { 19 | address parent; 20 | address left; 21 | address right; 22 | bool red; 23 | } 24 | 25 | address public root; 26 | address constant EMPTY = address(0); 27 | 28 | mapping(address => Node) public nodes; 29 | 30 | function exists(address key) internal view returns (bool) { 31 | return (key != EMPTY) && ((key == root) || (nodes[key].parent != EMPTY)); 32 | } 33 | 34 | function sortKey(address key) internal view virtual returns (uint256); 35 | 36 | function rotateLeft(address key) internal { 37 | address cursor = nodes[key].right; 38 | address keyParent = nodes[key].parent; 39 | address cursorLeft = nodes[cursor].left; 40 | nodes[key].right = cursorLeft; 41 | if (cursorLeft != EMPTY) { 42 | nodes[cursorLeft].parent = key; 43 | } 44 | nodes[cursor].parent = keyParent; 45 | if (keyParent == EMPTY) { 46 | root = cursor; 47 | } else if (key == nodes[keyParent].left) { 48 | nodes[keyParent].left = cursor; 49 | } else { 50 | nodes[keyParent].right = cursor; 51 | } 52 | nodes[cursor].left = key; 53 | nodes[key].parent = cursor; 54 | } 55 | 56 | function rotateRight(address key) internal { 57 | address cursor = nodes[key].left; 58 | address keyParent = nodes[key].parent; 59 | address cursorRight = nodes[cursor].right; 60 | nodes[key].left = cursorRight; 61 | if (cursorRight != EMPTY) { 62 | nodes[cursorRight].parent = key; 63 | } 64 | nodes[cursor].parent = keyParent; 65 | if (keyParent == EMPTY) { 66 | root = cursor; 67 | } else if (key == nodes[keyParent].right) { 68 | nodes[keyParent].right = cursor; 69 | } else { 70 | nodes[keyParent].left = cursor; 71 | } 72 | nodes[cursor].right = key; 73 | nodes[key].parent = cursor; 74 | } 75 | 76 | function insertFixup(address key) internal { 77 | address cursor; 78 | while (key != root && nodes[nodes[key].parent].red) { 79 | address keyParent = nodes[key].parent; 80 | if (keyParent == nodes[nodes[keyParent].parent].left) { 81 | cursor = nodes[nodes[keyParent].parent].right; 82 | if (nodes[cursor].red) { 83 | nodes[keyParent].red = false; 84 | nodes[cursor].red = false; 85 | nodes[nodes[keyParent].parent].red = true; 86 | key = nodes[keyParent].parent; 87 | } else { 88 | if (key == nodes[keyParent].right) { 89 | key = keyParent; 90 | rotateLeft(key); 91 | } 92 | keyParent = nodes[key].parent; 93 | nodes[keyParent].red = false; 94 | nodes[nodes[keyParent].parent].red = true; 95 | rotateRight(nodes[keyParent].parent); 96 | } 97 | } else { 98 | cursor = nodes[nodes[keyParent].parent].left; 99 | if (nodes[cursor].red) { 100 | nodes[keyParent].red = false; 101 | nodes[cursor].red = false; 102 | nodes[nodes[keyParent].parent].red = true; 103 | key = nodes[keyParent].parent; 104 | } else { 105 | if (key == nodes[keyParent].left) { 106 | key = keyParent; 107 | rotateRight(key); 108 | } 109 | keyParent = nodes[key].parent; 110 | nodes[keyParent].red = false; 111 | nodes[nodes[keyParent].parent].red = true; 112 | rotateLeft(nodes[keyParent].parent); 113 | } 114 | } 115 | } 116 | if (nodes[root].red) nodes[root].red = false; 117 | } 118 | 119 | function insert(address key) internal { 120 | address cursor = EMPTY; 121 | address probe = root; 122 | while (probe != EMPTY) { 123 | cursor = probe; 124 | if (sortKey(key) < sortKey(probe)) { 125 | probe = nodes[probe].left; 126 | } else { 127 | probe = nodes[probe].right; 128 | } 129 | } 130 | nodes[key] = Node({parent: cursor, left: EMPTY, right: EMPTY, red: true}); 131 | if (cursor == EMPTY) { 132 | root = key; 133 | } else if (sortKey(key) < sortKey(cursor)) { 134 | nodes[cursor].left = key; 135 | } else { 136 | nodes[cursor].right = key; 137 | } 138 | insertFixup(key); 139 | } 140 | 141 | function replaceParent(address a, address b) internal { 142 | address bParent = nodes[b].parent; 143 | nodes[a].parent = bParent; 144 | if (bParent == EMPTY) { 145 | root = a; 146 | } else { 147 | if (b == nodes[bParent].left) { 148 | nodes[bParent].left = a; 149 | } else { 150 | nodes[bParent].right = a; 151 | } 152 | } 153 | } 154 | 155 | function removeFixup(address key) internal { 156 | address cursor; 157 | while (key != root && !nodes[key].red) { 158 | address keyParent = nodes[key].parent; 159 | if (key == nodes[keyParent].left) { 160 | cursor = nodes[keyParent].right; 161 | if (nodes[cursor].red) { 162 | nodes[cursor].red = false; 163 | nodes[keyParent].red = true; 164 | rotateLeft(keyParent); 165 | cursor = nodes[keyParent].right; 166 | } 167 | if (!nodes[nodes[cursor].left].red && !nodes[nodes[cursor].right].red) { 168 | nodes[cursor].red = true; 169 | key = keyParent; 170 | } else { 171 | if (!nodes[nodes[cursor].right].red) { 172 | nodes[nodes[cursor].left].red = false; 173 | nodes[cursor].red = true; 174 | rotateRight(cursor); 175 | cursor = nodes[keyParent].right; 176 | } 177 | nodes[cursor].red = nodes[keyParent].red; 178 | nodes[keyParent].red = false; 179 | nodes[nodes[cursor].right].red = false; 180 | rotateLeft(keyParent); 181 | return; // key = root; 182 | } 183 | } else { 184 | cursor = nodes[keyParent].left; 185 | if (nodes[cursor].red) { 186 | nodes[cursor].red = false; 187 | nodes[keyParent].red = true; 188 | rotateRight(keyParent); 189 | cursor = nodes[keyParent].left; 190 | } 191 | if (!nodes[nodes[cursor].right].red && !nodes[nodes[cursor].left].red) { 192 | nodes[cursor].red = true; 193 | key = keyParent; 194 | } else { 195 | if (!nodes[nodes[cursor].left].red) { 196 | nodes[nodes[cursor].right].red = false; 197 | nodes[cursor].red = true; 198 | rotateLeft(cursor); 199 | cursor = nodes[keyParent].left; 200 | } 201 | nodes[cursor].red = nodes[keyParent].red; 202 | nodes[keyParent].red = false; 203 | nodes[nodes[cursor].left].red = false; 204 | rotateRight(keyParent); 205 | return; // key = root; 206 | } 207 | } 208 | } 209 | if (nodes[key].red) nodes[key].red = false; 210 | } 211 | 212 | function remove(address key) internal { 213 | address probe; 214 | address cursor; 215 | if (nodes[key].left == EMPTY || nodes[key].right == EMPTY) { 216 | cursor = key; 217 | } else { 218 | cursor = nodes[key].right; 219 | while (nodes[cursor].left != EMPTY) { 220 | cursor = nodes[cursor].left; 221 | } 222 | } 223 | if (nodes[cursor].left != EMPTY) { 224 | probe = nodes[cursor].left; 225 | } else { 226 | probe = nodes[cursor].right; 227 | } 228 | address yParent = nodes[cursor].parent; 229 | nodes[probe].parent = yParent; 230 | if (yParent != EMPTY) { 231 | if (cursor == nodes[yParent].left) { 232 | nodes[yParent].left = probe; 233 | } else { 234 | nodes[yParent].right = probe; 235 | } 236 | } else { 237 | root = probe; 238 | } 239 | bool doFixup = !nodes[cursor].red; 240 | if (cursor != key) { 241 | replaceParent(cursor, key); 242 | nodes[cursor].left = nodes[key].left; 243 | nodes[nodes[cursor].left].parent = cursor; 244 | nodes[cursor].right = nodes[key].right; 245 | nodes[nodes[cursor].right].parent = cursor; 246 | nodes[cursor].red = nodes[key].red; 247 | (cursor, key) = (key, cursor); 248 | } 249 | if (doFixup) { 250 | removeFixup(probe); 251 | } 252 | delete nodes[cursor]; 253 | } 254 | } 255 | // ---------------------------------------------------------------------------- 256 | // End - BokkyPooBah's Red-Black Tree Library 257 | // ---------------------------------------------------------------------------- 258 | -------------------------------------------------------------------------------- /src/DepositVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./InnerProductVerifier.sol"; 6 | import "./Utils.sol"; 7 | 8 | contract DepositVerifier { 9 | using Utils for uint256; 10 | using Utils for Utils.Point; 11 | 12 | InnerProductVerifier immutable _ip; 13 | 14 | constructor(InnerProductVerifier ip_) { 15 | _ip = ip_; 16 | } 17 | 18 | function g() internal view returns (Utils.Point memory) { 19 | return Utils.Point(_ip.gX(), _ip.gY()); 20 | } 21 | 22 | function h() internal view returns (Utils.Point memory) { 23 | return Utils.Point(_ip.hX(), _ip.hY()); 24 | } 25 | 26 | function gs(uint256 i) internal view returns (Utils.Point memory) { 27 | (bytes32 x, bytes32 y) = _ip.gs(i); 28 | return Utils.Point(x, y); 29 | } 30 | 31 | function hs(uint256 i) internal view returns (Utils.Point memory) { 32 | (bytes32 x, bytes32 y) = _ip.hs(i); 33 | return Utils.Point(x, y); 34 | } 35 | 36 | struct Locals { 37 | uint256 v; 38 | uint256 w; 39 | uint256 vPow; 40 | uint256 wPow; 41 | uint256[n][2] f; // could just allocate extra space in the proof? 42 | uint256[N] r; // each poly is an array of length N. evaluations of prods 43 | Utils.Point temp; 44 | Utils.Point C_XR; 45 | Utils.Point y_XR; 46 | uint256 c; 47 | Utils.Point A_D; 48 | Utils.Point A_X; 49 | } 50 | 51 | function verify(uint256 amount, Utils.Statement calldata statement, Utils.DepositProof calldata proof) external view { 52 | Locals memory locals; 53 | locals.v = uint256(keccak256(abi.encode(amount, statement.Y, statement.C, statement.D, proof.A, proof.B))).mod(); 54 | locals.w = uint256(keccak256(abi.encode(locals.v, proof.C_XG, proof.y_XG))).mod(); 55 | for (uint256 k = 0; k < n; k++) { 56 | locals.f[1][k] = proof.f[k]; 57 | locals.f[0][k] = locals.w.sub(proof.f[k]); 58 | 59 | locals.temp = locals.temp.add(gs(k).mul(locals.f[1][k])); 60 | locals.temp = locals.temp.add(hs(k).mul(locals.f[1][k].mul(locals.f[0][k]))); 61 | } 62 | require(proof.B.mul(locals.w).add(proof.A).eq(locals.temp.add(h().mul(proof.z_A))), "Bit-proof verification failed."); 63 | 64 | locals.r = Utils.assemblePolynomials(locals.f); 65 | locals.wPow = 1; 66 | for (uint256 k = 0; k < n; k++) { 67 | locals.C_XR = locals.C_XR.add(proof.C_XG[k].mul(locals.wPow.neg())); 68 | locals.y_XR = locals.y_XR.add(proof.y_XG[k].mul(locals.wPow.neg())); 69 | 70 | locals.wPow = locals.wPow.mul(locals.w); 71 | } 72 | locals.vPow = locals.v; // used to be 1 73 | for (uint256 i = 0; i < N; i++) { 74 | uint256 multiplier = locals.r[i].add(locals.vPow.mul(locals.wPow.sub(locals.r[i]))); // locals. ? 75 | locals.C_XR = locals.C_XR.add(statement.C[i].mul(multiplier)); 76 | locals.y_XR = locals.y_XR.add(statement.Y[i].mul(multiplier)); 77 | locals.vPow = locals.vPow.mul(locals.v); // used to do this only if (i > 0) 78 | } 79 | locals.C_XR = locals.C_XR.add(g().mul(amount.neg().mul(locals.wPow))); // this line is new 80 | 81 | locals.A_D = g().mul(proof.s_r).add(statement.D.mul(proof.c.neg())); // add(mul(locals.gR, proof.s_r), mul(locals.DR, proof.c.neg())); 82 | locals.A_X = locals.y_XR.mul(proof.s_r).add(locals.C_XR.mul(proof.c.neg())); 83 | 84 | locals.c = uint256(keccak256(abi.encode(locals.v, locals.A_D, locals.A_X))).mod(); 85 | require(locals.c == proof.c, "Sigma protocol failure."); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./BalanceTree.sol"; 7 | 8 | /** 9 | * @dev Implementation of the {IERC20} interface. 10 | * 11 | * This implementation is agnostic to the way tokens are created. This means 12 | * that a supply mechanism has to be added in a derived contract using {_mint}. 13 | * For a generic mechanism see {ERC20PresetMinterPauser}. 14 | * 15 | * TIP: For a detailed writeup see our guide 16 | * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How 17 | * to implement supply mechanisms]. 18 | * 19 | * We have followed general OpenZeppelin Contracts guidelines: functions revert 20 | * instead returning `false` on failure. This behavior is nonetheless 21 | * conventional and does not conflict with the expectations of ERC20 22 | * applications. 23 | * 24 | * Additionally, an {Approval} event is emitted on calls to {transferFrom}. 25 | * This allows applications to reconstruct the allowance for all accounts just 26 | * by listening to said events. Other implementations of the EIP may not emit 27 | * these events, as it isn't required by the specification. 28 | * 29 | * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} 30 | * functions have been added to mitigate the well-known issues around setting 31 | * allowances. See {IERC20-approve}. 32 | */ 33 | contract ERC20 is BalanceTree { 34 | mapping(address => uint256) private _balances; 35 | 36 | mapping(address => mapping(address => uint256)) private _allowances; 37 | 38 | uint256 private _totalSupply; 39 | 40 | string private _name; 41 | string private _symbol; 42 | 43 | function sortKey(address key) internal view override returns (uint256) { 44 | return _balances[key]; 45 | } 46 | 47 | /** 48 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 49 | * another (`to`). 50 | * 51 | * Note that `value` may be zero. 52 | */ 53 | event Transfer(address indexed from, address indexed to, uint256 value); 54 | 55 | /** 56 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 57 | * a call to {approve}. `value` is the new allowance. 58 | */ 59 | event Approval(address indexed owner, address indexed spender, uint256 value); 60 | 61 | /** 62 | * @dev Sets the values for {name} and {symbol}. 63 | * 64 | * The default value of {decimals} is 18. To select a different value for 65 | * {decimals} you should overload it. 66 | * 67 | * All two of these values are immutable: they can only be set once during 68 | * construction. 69 | */ 70 | constructor(string memory name_, string memory symbol_) { 71 | _name = name_; 72 | _symbol = symbol_; 73 | _mint(msg.sender, 1000000 * 10e18); 74 | } 75 | 76 | /** 77 | * @dev Returns the name of the token. 78 | */ 79 | function name() public view returns (string memory) { 80 | return _name; 81 | } 82 | 83 | /** 84 | * @dev Returns the symbol of the token, usually a shorter version of the 85 | * name. 86 | */ 87 | function symbol() public view returns (string memory) { 88 | return _symbol; 89 | } 90 | 91 | /** 92 | * @dev Returns the number of decimals used to get its user representation. 93 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 94 | * be displayed to a user as `5.05` (`505 / 10 ** 2`). 95 | * 96 | * Tokens usually opt for a value of 18, imitating the relationship between 97 | * Ether and Wei. This is the value {ERC20} uses, unless this function is 98 | * overridden; 99 | * 100 | * NOTE: This information is only used for _display_ purposes: it in 101 | * no way affects any of the arithmetic of the contract, including 102 | * {IERC20-balanceOf} and {IERC20-transfer}. 103 | */ 104 | function decimals() public pure returns (uint8) { 105 | return 18; 106 | } 107 | 108 | /** 109 | * @dev See {IERC20-totalSupply}. 110 | */ 111 | function totalSupply() public view returns (uint256) { 112 | return _totalSupply; 113 | } 114 | 115 | /** 116 | * @dev See {IERC20-balanceOf}. 117 | */ 118 | function balanceOf(address account) public view returns (uint256) { 119 | return _balances[account]; 120 | } 121 | 122 | /** 123 | * @dev See {IERC20-transfer}. 124 | * 125 | * Requirements: 126 | * 127 | * - `to` cannot be the zero address. 128 | * - the caller must have a balance of at least `amount`. 129 | */ 130 | function transfer(address to, uint256 amount) public returns (bool) { 131 | address owner = msg.sender; 132 | _transfer(owner, to, amount); 133 | return true; 134 | } 135 | 136 | /** 137 | * @dev See {IERC20-allowance}. 138 | */ 139 | function allowance(address owner, address spender) public view returns (uint256) { 140 | return _allowances[owner][spender]; 141 | } 142 | 143 | /** 144 | * @dev See {IERC20-approve}. 145 | * 146 | * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on 147 | * `transferFrom`. This is semantically equivalent to an infinite approval. 148 | * 149 | * Requirements: 150 | * 151 | * - `spender` cannot be the zero address. 152 | */ 153 | function approve(address spender, uint256 amount) public returns (bool) { 154 | address owner = msg.sender; 155 | _approve(owner, spender, amount); 156 | return true; 157 | } 158 | 159 | /** 160 | * @dev See {IERC20-transferFrom}. 161 | * 162 | * Emits an {Approval} event indicating the updated allowance. This is not 163 | * required by the EIP. See the note at the beginning of {ERC20}. 164 | * 165 | * NOTE: Does not update the allowance if the current allowance 166 | * is the maximum `uint256`. 167 | * 168 | * Requirements: 169 | * 170 | * - `from` and `to` cannot be the zero address. 171 | * - `from` must have a balance of at least `amount`. 172 | * - the caller must have allowance for ``from``'s tokens of at least 173 | * `amount`. 174 | */ 175 | function transferFrom(address from, address to, uint256 amount) public returns (bool) { 176 | address spender = msg.sender; 177 | _spendAllowance(from, spender, amount); 178 | _transfer(from, to, amount); 179 | return true; 180 | } 181 | 182 | /** 183 | * @dev Atomically increases the allowance granted to `spender` by the caller. 184 | * 185 | * This is an alternative to {approve} that can be used as a mitigation for 186 | * problems described in {IERC20-approve}. 187 | * 188 | * Emits an {Approval} event indicating the updated allowance. 189 | * 190 | * Requirements: 191 | * 192 | * - `spender` cannot be the zero address. 193 | */ 194 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 195 | address owner = msg.sender; 196 | _approve(owner, spender, allowance(owner, spender) + addedValue); 197 | return true; 198 | } 199 | 200 | /** 201 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 202 | * 203 | * This is an alternative to {approve} that can be used as a mitigation for 204 | * problems described in {IERC20-approve}. 205 | * 206 | * Emits an {Approval} event indicating the updated allowance. 207 | * 208 | * Requirements: 209 | * 210 | * - `spender` cannot be the zero address. 211 | * - `spender` must have allowance for the caller of at least 212 | * `subtractedValue`. 213 | */ 214 | function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { 215 | address owner = msg.sender; 216 | uint256 currentAllowance = allowance(owner, spender); 217 | require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); 218 | unchecked { 219 | _approve(owner, spender, currentAllowance - subtractedValue); 220 | } 221 | 222 | return true; 223 | } 224 | 225 | /** 226 | * @dev Moves `amount` of tokens from `from` to `to`. 227 | * 228 | * This internal function is equivalent to {transfer}, and can be used to 229 | * e.g. implement automatic token fees, slashing mechanisms, etc. 230 | * 231 | * Emits a {Transfer} event. 232 | * 233 | * Requirements: 234 | * 235 | * - `from` cannot be the zero address. 236 | * - `to` cannot be the zero address. 237 | * - `from` must have a balance of at least `amount`. 238 | */ 239 | function _transfer(address from, address to, uint256 amount) internal { 240 | require(from != address(0), "ERC20: transfer from the zero address"); 241 | require(to != address(0), "ERC20: transfer to the zero address"); 242 | 243 | _beforeTokenTransfer(from, to, amount); 244 | 245 | uint256 fromBalance = _balances[from]; 246 | require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); 247 | unchecked { 248 | _balances[from] = fromBalance - amount; 249 | } 250 | _balances[to] += amount; 251 | 252 | if (exists(from)) remove(from); // could be false, if balance was originally 0 and you transfer 0 253 | if (_balances[from] > 0) insert(from); // could be false, if from got emptied 254 | if (exists(to)) remove(to); // could be false, if to is new 255 | if (_balances[to] > 0) insert(to); // could be false, if 0 got transferred and was already 0. 256 | 257 | emit Transfer(from, to, amount); 258 | 259 | _afterTokenTransfer(from, to, amount); 260 | } 261 | 262 | /** 263 | * @dev Creates `amount` tokens and assigns them to `account`, increasing 264 | * the total supply. 265 | * 266 | * Emits a {Transfer} event with `from` set to the zero address. 267 | * 268 | * Requirements: 269 | * 270 | * - `account` cannot be the zero address. 271 | */ 272 | function _mint(address account, uint256 amount) internal { 273 | require(account != address(0), "ERC20: mint to the zero address"); 274 | 275 | _beforeTokenTransfer(address(0), account, amount); 276 | 277 | _totalSupply += amount; 278 | _balances[account] += amount; 279 | 280 | if (exists(account)) remove(account); 281 | if (_balances[account] > 0) insert(account); 282 | 283 | emit Transfer(address(0), account, amount); 284 | 285 | _afterTokenTransfer(address(0), account, amount); 286 | } 287 | 288 | /** 289 | * @dev Destroys `amount` tokens from `account`, reducing the 290 | * total supply. 291 | * 292 | * Emits a {Transfer} event with `to` set to the zero address. 293 | * 294 | * Requirements: 295 | * 296 | * - `account` cannot be the zero address. 297 | * - `account` must have at least `amount` tokens. 298 | */ 299 | function _burn(address account, uint256 amount) internal { 300 | require(account != address(0), "ERC20: burn from the zero address"); 301 | 302 | _beforeTokenTransfer(account, address(0), amount); 303 | 304 | uint256 accountBalance = _balances[account]; 305 | 306 | require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); 307 | unchecked { 308 | _balances[account] = accountBalance - amount; 309 | } 310 | _totalSupply -= amount; 311 | 312 | if (exists(account)) remove(account); // could be false, if was 0 before burn and you burn 0 313 | if (_balances[account] > 0) insert(account); 314 | 315 | emit Transfer(account, address(0), amount); 316 | 317 | _afterTokenTransfer(account, address(0), amount); 318 | } 319 | 320 | /** 321 | * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. 322 | * 323 | * This internal function is equivalent to `approve`, and can be used to 324 | * e.g. set automatic allowances for certain subsystems, etc. 325 | * 326 | * Emits an {Approval} event. 327 | * 328 | * Requirements: 329 | * 330 | * - `owner` cannot be the zero address. 331 | * - `spender` cannot be the zero address. 332 | */ 333 | function _approve(address owner, address spender, uint256 amount) internal { 334 | require(owner != address(0), "ERC20: approve from the zero address"); 335 | require(spender != address(0), "ERC20: approve to the zero address"); 336 | 337 | _allowances[owner][spender] = amount; 338 | emit Approval(owner, spender, amount); 339 | } 340 | 341 | /** 342 | * @dev Updates `owner` s allowance for `spender` based on spent `amount`. 343 | * 344 | * Does not update the allowance amount in case of infinite allowance. 345 | * Revert if not enough allowance is available. 346 | * 347 | * Might emit an {Approval} event. 348 | */ 349 | function _spendAllowance(address owner, address spender, uint256 amount) internal { 350 | uint256 currentAllowance = allowance(owner, spender); 351 | if (currentAllowance != type(uint256).max) { 352 | require(currentAllowance >= amount, "ERC20: insufficient allowance"); 353 | unchecked { 354 | _approve(owner, spender, currentAllowance - amount); 355 | } 356 | } 357 | } 358 | 359 | /** 360 | * @dev Hook that is called before any transfer of tokens. This includes 361 | * minting and burning. 362 | * 363 | * Calling conditions: 364 | * 365 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 366 | * will be transferred to `to`. 367 | * - when `from` is zero, `amount` tokens will be minted for `to`. 368 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 369 | * - `from` and `to` are never both zero. 370 | * 371 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 372 | */ 373 | function _beforeTokenTransfer(address from, address to, uint256 amount) internal {} 374 | 375 | /** 376 | * @dev Hook that is called after any transfer of tokens. This includes 377 | * minting and burning. 378 | * 379 | * Calling conditions: 380 | * 381 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 382 | * has been transferred to `to`. 383 | * - when `from` is zero, `amount` tokens have been minted for `to`. 384 | * - when `to` is zero, `amount` of ``from``'s tokens have been burned. 385 | * - `from` and `to` are never both zero. 386 | * 387 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 388 | */ 389 | function _afterTokenTransfer(address from, address to, uint256 amount) internal {} 390 | } 391 | // ---------------------------------------------------------------------------- 392 | // End - BokkyPooBah's Red-Black Tree Library 393 | // ---------------------------------------------------------------------------- 394 | -------------------------------------------------------------------------------- /src/EpochTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | // ---------------------------------------------------------------------------- 6 | // BokkyPooBah's Red-Black Tree Library v1.0-pre-release-a 7 | // 8 | // A Solidity Red-Black Tree binary search library to store and access a sorted 9 | // list of unsigned integer data. The Red-Black algorithm rebalances the binary 10 | // search tree, resulting in O(log n) insert, remove and search time (and ~gas) 11 | // 12 | // https://github.com/bokkypoobah/BokkyPooBahsRedBlackTreeLibrary 13 | // 14 | // 15 | // Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2020. The MIT Licence. 16 | // ---------------------------------------------------------------------------- 17 | contract EpochTree { 18 | struct Node { 19 | uint64 parent; 20 | uint64 left; 21 | uint64 right; 22 | bool red; 23 | } 24 | 25 | uint64 public root; 26 | uint64 public blackHeight; 27 | uint64 constant EMPTY = 0; 28 | 29 | mapping(uint64 => Node) public nodes; 30 | 31 | function exists(uint64 key) public view returns (bool) { 32 | // need public for FirnLogic.sol 33 | return (key != EMPTY) && ((key == root) || (nodes[key].parent != EMPTY)); 34 | } 35 | 36 | function rotateLeft(uint64 key) internal { 37 | uint64 cursor = nodes[key].right; 38 | uint64 keyParent = nodes[key].parent; 39 | uint64 cursorLeft = nodes[cursor].left; 40 | nodes[key].right = cursorLeft; 41 | if (cursorLeft != EMPTY) { 42 | nodes[cursorLeft].parent = key; 43 | } 44 | nodes[cursor].parent = keyParent; 45 | if (keyParent == EMPTY) { 46 | root = cursor; 47 | } else if (key == nodes[keyParent].left) { 48 | nodes[keyParent].left = cursor; 49 | } else { 50 | nodes[keyParent].right = cursor; 51 | } 52 | nodes[cursor].left = key; 53 | nodes[key].parent = cursor; 54 | } 55 | 56 | function rotateRight(uint64 key) internal { 57 | uint64 cursor = nodes[key].left; 58 | uint64 keyParent = nodes[key].parent; 59 | uint64 cursorRight = nodes[cursor].right; 60 | nodes[key].left = cursorRight; 61 | if (cursorRight != EMPTY) { 62 | nodes[cursorRight].parent = key; 63 | } 64 | nodes[cursor].parent = keyParent; 65 | if (keyParent == EMPTY) { 66 | root = cursor; 67 | } else if (key == nodes[keyParent].right) { 68 | nodes[keyParent].right = cursor; 69 | } else { 70 | nodes[keyParent].left = cursor; 71 | } 72 | nodes[cursor].right = key; 73 | nodes[key].parent = cursor; 74 | } 75 | 76 | function insertFixup(uint64 key) internal { 77 | uint64 cursor; 78 | while (key != root && nodes[nodes[key].parent].red) { 79 | uint64 keyParent = nodes[key].parent; 80 | if (keyParent == nodes[nodes[keyParent].parent].left) { 81 | cursor = nodes[nodes[keyParent].parent].right; 82 | if (nodes[cursor].red) { 83 | nodes[keyParent].red = false; 84 | nodes[cursor].red = false; 85 | nodes[nodes[keyParent].parent].red = true; 86 | key = nodes[keyParent].parent; 87 | } else { 88 | if (key == nodes[keyParent].right) { 89 | key = keyParent; 90 | rotateLeft(key); 91 | } 92 | keyParent = nodes[key].parent; 93 | nodes[keyParent].red = false; 94 | nodes[nodes[keyParent].parent].red = true; 95 | rotateRight(nodes[keyParent].parent); 96 | } 97 | } else { 98 | cursor = nodes[nodes[keyParent].parent].left; 99 | if (nodes[cursor].red) { 100 | nodes[keyParent].red = false; 101 | nodes[cursor].red = false; 102 | nodes[nodes[keyParent].parent].red = true; 103 | key = nodes[keyParent].parent; 104 | } else { 105 | if (key == nodes[keyParent].left) { 106 | key = keyParent; 107 | rotateRight(key); 108 | } 109 | keyParent = nodes[key].parent; 110 | nodes[keyParent].red = false; 111 | nodes[nodes[keyParent].parent].red = true; 112 | rotateLeft(nodes[keyParent].parent); 113 | } 114 | } 115 | } 116 | if (nodes[root].red) { 117 | nodes[root].red = false; 118 | blackHeight++; 119 | } 120 | } 121 | 122 | function insert(uint64 key) internal { 123 | uint64 cursor = EMPTY; 124 | uint64 probe = root; 125 | while (probe != EMPTY) { 126 | cursor = probe; 127 | if (key < probe) { 128 | probe = nodes[probe].left; 129 | } else { 130 | probe = nodes[probe].right; 131 | } 132 | } 133 | nodes[key] = Node({parent: cursor, left: EMPTY, right: EMPTY, red: true}); 134 | if (cursor == EMPTY) { 135 | root = key; 136 | } else if (key < cursor) { 137 | nodes[cursor].left = key; 138 | } else { 139 | nodes[cursor].right = key; 140 | } 141 | insertFixup(key); 142 | } 143 | 144 | function replaceParent(uint64 a, uint64 b) internal { 145 | uint64 bParent = nodes[b].parent; 146 | nodes[a].parent = bParent; 147 | if (bParent == EMPTY) { 148 | root = a; 149 | } else { 150 | if (b == nodes[bParent].left) { 151 | nodes[bParent].left = a; 152 | } else { 153 | nodes[bParent].right = a; 154 | } 155 | } 156 | } 157 | 158 | function removeFixup(uint64 key) internal { 159 | uint64 cursor; 160 | while (key != root && !nodes[key].red) { 161 | uint64 keyParent = nodes[key].parent; 162 | if (key == nodes[keyParent].left) { 163 | cursor = nodes[keyParent].right; 164 | if (nodes[cursor].red) { 165 | nodes[cursor].red = false; 166 | nodes[keyParent].red = true; 167 | rotateLeft(keyParent); 168 | cursor = nodes[keyParent].right; 169 | } 170 | if (!nodes[nodes[cursor].left].red && !nodes[nodes[cursor].right].red) { 171 | nodes[cursor].red = true; 172 | key = keyParent; 173 | } else { 174 | if (!nodes[nodes[cursor].right].red) { 175 | nodes[nodes[cursor].left].red = false; 176 | nodes[cursor].red = true; 177 | rotateRight(cursor); 178 | cursor = nodes[keyParent].right; 179 | } 180 | nodes[cursor].red = nodes[keyParent].red; 181 | nodes[keyParent].red = false; 182 | nodes[nodes[cursor].right].red = false; 183 | rotateLeft(keyParent); 184 | return; // key = root; 185 | } 186 | } else { 187 | cursor = nodes[keyParent].left; 188 | if (nodes[cursor].red) { 189 | nodes[cursor].red = false; 190 | nodes[keyParent].red = true; 191 | rotateRight(keyParent); 192 | cursor = nodes[keyParent].left; 193 | } 194 | if (!nodes[nodes[cursor].right].red && !nodes[nodes[cursor].left].red) { 195 | nodes[cursor].red = true; 196 | key = keyParent; 197 | } else { 198 | if (!nodes[nodes[cursor].left].red) { 199 | nodes[nodes[cursor].right].red = false; 200 | nodes[cursor].red = true; 201 | rotateLeft(cursor); 202 | cursor = nodes[keyParent].left; 203 | } 204 | nodes[cursor].red = nodes[keyParent].red; 205 | nodes[keyParent].red = false; 206 | nodes[nodes[cursor].left].red = false; 207 | rotateRight(keyParent); 208 | return; // key = root; 209 | } 210 | } 211 | } 212 | if (nodes[key].red) nodes[key].red = false; 213 | else blackHeight--; 214 | } 215 | 216 | function remove(uint64 key) internal { 217 | uint64 probe; 218 | uint64 cursor; 219 | if (nodes[key].left == EMPTY || nodes[key].right == EMPTY) { 220 | cursor = key; 221 | } else { 222 | cursor = nodes[key].right; 223 | while (nodes[cursor].left != EMPTY) { 224 | cursor = nodes[cursor].left; 225 | } 226 | } 227 | if (nodes[cursor].left != EMPTY) { 228 | probe = nodes[cursor].left; 229 | } else { 230 | probe = nodes[cursor].right; 231 | } 232 | uint64 yParent = nodes[cursor].parent; 233 | nodes[probe].parent = yParent; 234 | if (yParent != EMPTY) { 235 | if (cursor == nodes[yParent].left) { 236 | nodes[yParent].left = probe; 237 | } else { 238 | nodes[yParent].right = probe; 239 | } 240 | } else { 241 | root = probe; 242 | } 243 | bool doFixup = !nodes[cursor].red; 244 | if (cursor != key) { 245 | replaceParent(cursor, key); 246 | nodes[cursor].left = nodes[key].left; 247 | nodes[nodes[cursor].left].parent = cursor; 248 | nodes[cursor].right = nodes[key].right; 249 | nodes[nodes[cursor].right].parent = cursor; 250 | nodes[cursor].red = nodes[key].red; 251 | (cursor, key) = (key, cursor); 252 | } 253 | if (doFixup) { 254 | removeFixup(probe); 255 | } 256 | delete nodes[cursor]; 257 | } 258 | } 259 | // ---------------------------------------------------------------------------- 260 | // End - BokkyPooBah's Red-Black Tree Library 261 | // ---------------------------------------------------------------------------- 262 | -------------------------------------------------------------------------------- /src/Firn.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./EpochTree.sol"; 6 | import "./DepositVerifier.sol"; 7 | import "./TransferVerifier.sol"; 8 | import "./WithdrawalVerifier.sol"; 9 | import "./Utils.sol"; 10 | 11 | contract Firn is EpochTree { 12 | using Utils for uint256; 13 | using Utils for Utils.Point; 14 | 15 | mapping(bytes32 => Utils.Point[2]) _acc; // main account mapping 16 | mapping(bytes32 => Utils.Point[2]) _pending; // storage for pending transfers 17 | mapping(bytes32 => uint64) _lastRollOver; 18 | bytes32[] _nonces; // would be more natural to use a mapping (really a set), but they can't be deleted / reset! 19 | uint64 _lastGlobalUpdate = 0; // will be also used as a proxy for "current epoch", seeing as rollovers will be anticipated 20 | 21 | uint256 constant EPOCH_LENGTH = 60; 22 | uint32 constant _fee = 128; // fee will be floor(amount / 128) 23 | 24 | DepositVerifier immutable _deposit; 25 | TransferVerifier immutable _transfer; 26 | WithdrawalVerifier immutable _withdrawal; 27 | address immutable _treasury; 28 | 29 | event RegisterOccurred(address indexed sender, bytes32 indexed account, uint32 amount); 30 | event DepositOccurred(bytes32[N] Y, bytes32[N] C, bytes32 D, address indexed source, uint32 amount); // amount not indexed 31 | event TransferOccurred(bytes32[N] Y, bytes32[N] C, bytes32 D); 32 | event WithdrawalOccurred(bytes32[N] Y, bytes32[N] C, bytes32 D, uint32 amount, address indexed destination, bytes data); 33 | 34 | // some duplication here, but this is less painful than trying to retrieve it from the IP verifier / elsewhere. 35 | bytes32 immutable _gX; 36 | bytes32 immutable _gY; 37 | 38 | struct Info { 39 | // try to save storage space by using smaller int types here 40 | uint64 epoch; 41 | uint64 index; // index in the list 42 | uint64 amount; 43 | } 44 | 45 | mapping(bytes32 => Info) public info; // needs to be public, for reader 46 | mapping(uint64 => bytes32[]) public lists; // needs to be public, for reader 47 | 48 | function lengths(uint64 epoch) external view returns (uint256) { 49 | // see https://ethereum.stackexchange.com/a/20838. 50 | return lists[epoch].length; 51 | } 52 | 53 | constructor(DepositVerifier deposit_, TransferVerifier transfer_, WithdrawalVerifier withdrawal_, address treasury_) { 54 | _deposit = deposit_; 55 | _transfer = transfer_; 56 | _withdrawal = withdrawal_; 57 | _treasury = treasury_; 58 | 59 | Utils.Point memory gTemp = Utils.mapInto("g"); 60 | _gX = gTemp.x; 61 | _gY = gTemp.y; 62 | } 63 | 64 | function g() internal view returns (Utils.Point memory) { 65 | return Utils.Point(_gX, _gY); 66 | } 67 | 68 | function rollOver(bytes32 Y, uint64 epoch) internal { 69 | if (_lastRollOver[Y] < epoch) { 70 | _acc[Y][0] = _acc[Y][0].add(_pending[Y][0]); 71 | _acc[Y][1] = _acc[Y][1].add(_pending[Y][1]); 72 | delete _pending[Y]; // pending[Y] = [Utils.G1Point(0, 0), Utils.G1Point(0, 0)]; 73 | _lastRollOver[Y] = epoch; 74 | } 75 | } 76 | 77 | function touch(bytes32 Y, uint32 credit, uint64 epoch) internal { 78 | // could save a few operations if we check for the special case that current.epoch == epoch. 79 | bytes32[] storage list; // declare here not for efficiency, but to avoid shadowing warning 80 | Info storage current = info[Y]; 81 | if (current.epoch > 0) { 82 | // will only be false for registration...? 83 | list = lists[current.epoch]; 84 | list[current.index] = list[list.length - 1]; 85 | list.pop(); 86 | if (list.length == 0) remove(current.epoch); 87 | else if (current.index < list.length) info[list[current.index]].index = current.index; 88 | } 89 | current.epoch = epoch; 90 | current.amount += credit; // implicit conversion of RHS to uint64? 91 | if (!exists(epoch)) { 92 | insert(epoch); 93 | } 94 | list = lists[epoch]; 95 | current.index = uint32(list.length); 96 | list.push(Y); 97 | } 98 | 99 | function simulateAccounts(bytes32[] calldata Y, uint32 epoch) external view returns (bytes32[2][] memory result) { 100 | // interestingly, we lose no efficiency by accepting compressed, because we never have to decompress. 101 | result = new bytes32[2][](Y.length); 102 | for (uint256 i = 0; i < Y.length; i++) { 103 | Utils.Point[2] memory temp; 104 | temp[0] = _acc[Y[i]][0]; 105 | temp[1] = _acc[Y[i]][1]; 106 | if (_lastRollOver[Y[i]] < epoch) { 107 | temp[0] = temp[0].add(_pending[Y[i]][0]); 108 | temp[1] = temp[1].add(_pending[Y[i]][1]); 109 | } 110 | result[i][0] = Utils.compress(temp[0]); 111 | result[i][1] = Utils.compress(temp[1]); 112 | } 113 | } 114 | 115 | function register(bytes32 Y, bytes32[2] calldata signature) external payable { 116 | require(msg.value >= 1e16, "Must be at least 0.010 ETH."); 117 | require(msg.value % 1e15 == 0, "Must be a multiple of 0.001 ETH."); 118 | 119 | uint64 epoch = uint64(block.timestamp / EPOCH_LENGTH); 120 | rollOver(Y, epoch); 121 | 122 | require(address(this).balance <= 1e15 * 0xFFFFFFFF, "Escrow pool now too large."); 123 | uint32 credit = uint32(msg.value / 1e15); // >= 10. 124 | _pending[Y][0] = _pending[Y][0].add(g().mul(credit)); // convert to uint256? 125 | 126 | Utils.Point memory pub = Utils.decompress(Y); 127 | Utils.Point memory K = g().mul(uint256(signature[1])).add(pub.mul(uint256(signature[0]).neg())); 128 | uint256 c = uint256(keccak256(abi.encode("Welcome to Firn.", address(this), Y, K))).mod(); 129 | require(bytes32(c) == signature[0], "Signature failed to verify."); 130 | touch(Y, credit, epoch); 131 | 132 | emit RegisterOccurred(msg.sender, Y, credit); 133 | } 134 | 135 | function deposit(bytes32[N] calldata Y, bytes32[N] calldata C, bytes32 D, bytes calldata proof) external payable { 136 | // not doing a minimum amount here... the idea is that this function can't be used to force your way into the tree. 137 | require(msg.value % 1e15 == 0, "Must be a multiple of 0.001 ETH."); 138 | uint64 epoch = uint64(block.timestamp / EPOCH_LENGTH); 139 | require(address(this).balance <= 1e15 * 0xFFFFFFFF, "Escrow pool now too large."); 140 | uint32 credit = uint32(msg.value / 1e15); // can't overflow, by the above. 141 | 142 | emit DepositOccurred(Y, C, D, msg.sender, credit); 143 | 144 | Utils.Statement memory statement; 145 | statement.D = Utils.decompress(D); 146 | for (uint256 i = 0; i < N; i++) { 147 | bytes32 Y_i = Y[i]; 148 | rollOver(Y_i, epoch); 149 | 150 | statement.Y[i] = Utils.decompress(Y_i); 151 | statement.C[i] = Utils.decompress(C[i]); 152 | // mutate their pending, in advance of success. 153 | _pending[Y_i][0] = _pending[Y_i][0].add(statement.C[i]); 154 | _pending[Y_i][1] = _pending[Y_i][1].add(statement.D); 155 | require(info[Y_i].epoch > 0, "Only cached accounts allowed."); 156 | touch(Y_i, credit, epoch); // weird question whether this should be 0 or credit... revisit. 157 | } 158 | 159 | _deposit.verify(credit, statement, Utils.deserializeDeposit(proof)); 160 | } 161 | 162 | function transfer(bytes32[N] calldata Y, bytes32[N] calldata C, bytes32 D, bytes32 u, uint64 epoch, uint32 tip, bytes calldata proof) external { 163 | require(epoch == block.timestamp / EPOCH_LENGTH, "Wrong epoch."); // conversion of RHS to uint64 is unnecessary / redundant 164 | 165 | if (_lastGlobalUpdate < epoch) { 166 | _lastGlobalUpdate = epoch; 167 | delete _nonces; 168 | } 169 | for (uint256 i = 0; i < _nonces.length; i++) { 170 | require(_nonces[i] != u, "Nonce already seen."); 171 | } 172 | _nonces.push(u); 173 | 174 | emit TransferOccurred(Y, C, D); 175 | 176 | Utils.Statement memory statement; 177 | statement.D = Utils.decompress(D); 178 | for (uint256 i = 0; i < N; i++) { 179 | bytes32 Y_i = Y[i]; 180 | rollOver(Y_i, epoch); 181 | 182 | statement.Y[i] = Utils.decompress(Y_i); 183 | statement.C[i] = Utils.decompress(C[i]); 184 | statement.CLn[i] = _acc[Y_i][0].add(statement.C[i]); 185 | statement.CRn[i] = _acc[Y_i][1].add(statement.D); 186 | // mutate their pending, in advance of success. 187 | _pending[Y_i][0] = _pending[Y_i][0].add(statement.C[i]); 188 | _pending[Y_i][1] = _pending[Y_i][1].add(statement.D); 189 | require(info[Y_i].epoch > 0, "Only cached accounts allowed."); 190 | touch(Y_i, 0, epoch); 191 | } 192 | statement.epoch = epoch; 193 | statement.u = Utils.decompress(u); 194 | statement.fee = tip; 195 | 196 | _transfer.verify(statement, Utils.deserializeTransfer(proof)); 197 | 198 | payable(msg.sender).transfer(uint256(tip) * 1e15); 199 | } 200 | 201 | function withdraw(bytes32[N] calldata Y, bytes32[N] calldata C, bytes32 D, bytes32 u, uint64 epoch, uint32 amount, uint32 tip, bytes calldata proof, address destination, bytes calldata data) external { 202 | require(epoch == block.timestamp / EPOCH_LENGTH, "Wrong epoch."); // conversion of RHS to uint64 is unnecessary. // could supply epoch ourselves; check early to save gas 203 | 204 | if (_lastGlobalUpdate < epoch) { 205 | _lastGlobalUpdate = epoch; 206 | delete _nonces; 207 | } 208 | for (uint256 i = 0; i < _nonces.length; i++) { 209 | require(_nonces[i] != u, "Nonce already seen."); 210 | } 211 | _nonces.push(u); 212 | 213 | emit WithdrawalOccurred(Y, C, D, amount, destination, data); // emit here, because of stacktoodeep. 214 | 215 | Utils.Statement memory statement; 216 | statement.D = Utils.decompress(D); 217 | for (uint256 i = 0; i < N; i++) { 218 | bytes32 Y_i = Y[i]; 219 | rollOver(Y_i, epoch); 220 | 221 | statement.Y[i] = Utils.decompress(Y_i); 222 | statement.C[i] = Utils.decompress(C[i]); 223 | statement.CLn[i] = _acc[Y_i][0].add(statement.C[i]); 224 | statement.CRn[i] = _acc[Y_i][1].add(statement.D); 225 | // mutate their pending, in advance of success. 226 | _pending[Y_i][0] = _pending[Y_i][0].add(statement.C[i]); 227 | _pending[Y_i][1] = _pending[Y_i][1].add(statement.D); 228 | require(info[Y_i].epoch > 0, "Only cached accounts allowed."); 229 | } 230 | uint32 fee = amount / _fee; 231 | statement.epoch = epoch; // implicit conversion to uint256 232 | statement.u = Utils.decompress(u); 233 | statement.fee = tip + fee; // implicit conversion to uint256 234 | 235 | uint256 salt = uint256(keccak256(abi.encode(destination, data))); // .mod(); 236 | _withdrawal.verify(amount, statement, Utils.deserializeWithdrawal(proof), salt); 237 | 238 | payable(msg.sender).transfer(uint256(tip) * 1e15); 239 | (bool success,) = payable(_treasury).call{value: uint256(fee) * 1e15}(""); 240 | require(success, "External treasury call failed."); 241 | (success,) = payable(destination).call{value: uint256(amount) * 1e15}(data); 242 | require(success, "External withdrawal call failed."); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/FirnReader.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Firn.sol"; 6 | 7 | contract FirnReader { 8 | Firn immutable _firn; 9 | 10 | constructor(Firn firn_) { 11 | _firn = firn_; // actually pass the address of the proxy 12 | } 13 | 14 | function sampleAnonset(bytes32 seed, uint32 amount) external view returns (bytes32[N] memory result) { 15 | uint256 successes = 0; 16 | uint256 attempts = 0; 17 | while (successes < N) { 18 | attempts++; 19 | if (attempts > 50) { 20 | amount >>= 1; 21 | attempts = 0; 22 | } 23 | seed = keccak256(abi.encode(seed)); 24 | uint256 entropy = uint256(seed); 25 | uint256 layer = (entropy & 0xFFFFFFFF) % _firn.blackHeight(); 26 | entropy >>= 32; 27 | uint64 cursor = _firn.root(); 28 | bool red = false; // avoid a "shadowing" warning 29 | for (uint256 i = 0; i < layer; i++) { 30 | // inv: at the beginning of the loop, it points to the index-ith black node in the rightmost path. 31 | (,, cursor,) = _firn.nodes(cursor); // _firn.nodes[cursor].right 32 | (,,, red) = _firn.nodes(cursor); // if (_firn.nodes[cursor].red) 33 | if (red) (,, cursor,) = _firn.nodes(cursor); 34 | } 35 | uint256 subLayer; // (weighted) random element of {0, ..., blackHeight - 1 - layer}, low more likely. 36 | while (true) { 37 | bool found = false; 38 | for (uint256 i = 0; i < _firn.blackHeight() - layer; i++) { 39 | if (entropy & 0x01 == 0x01) { 40 | subLayer = i; 41 | found = true; 42 | break; 43 | } 44 | entropy >>= 1; 45 | } 46 | if (found) break; 47 | } 48 | entropy >>= 1; // always a 1 here. get rid of it. 49 | for (uint256 i = 0; i < _firn.blackHeight() - 1 - layer - subLayer; i++) { 50 | // at beginning of loop, points to the layer + ith black node down _random_ path... 51 | if (entropy & 0x01 == 0x01) (,, cursor,) = _firn.nodes(cursor); // cursor = _firn.nodes[cursor].right 52 | 53 | else (, cursor,,) = _firn.nodes(cursor); // cursor = _firn.nodes[cursor].left 54 | entropy >>= 1; 55 | (,,, red) = _firn.nodes(cursor); // if (_firn.nodes[cursor].red) 56 | if (red) { 57 | if (entropy & 0x01 == 0x01) (,, cursor,) = _firn.nodes(cursor); 58 | else (, cursor,,) = _firn.nodes(cursor); 59 | entropy >>= 1; 60 | } 61 | } 62 | (,, uint64 right,) = _firn.nodes(cursor); 63 | (,,, red) = _firn.nodes(right); 64 | if (entropy & 0x01 == 0x01 && red) { 65 | (,, cursor,) = _firn.nodes(cursor); 66 | } else if (entropy & 0x02 == 0x02) { 67 | (, uint64 left,,) = _firn.nodes(cursor); 68 | (,,, red) = _firn.nodes(left); 69 | if (red) (, cursor,,) = _firn.nodes(cursor); 70 | } 71 | entropy >>= 2; 72 | uint256 length = _firn.lengths(cursor); 73 | bytes32 account = _firn.lists(cursor, entropy % length); 74 | (, uint64 candidate,) = _firn.info(account); // what is the total amount this person has deposited? 75 | if (candidate < amount) continue; // skip them for now 76 | bool duplicate = false; 77 | for (uint256 i = 0; i < successes; i++) { 78 | if (result[i] == account) { 79 | duplicate = true; 80 | break; 81 | } 82 | } 83 | if (duplicate) continue; 84 | attempts = 0; 85 | result[successes++] = account; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/InnerProductVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Utils.sol"; 6 | 7 | contract InnerProductVerifier { 8 | using Utils for uint256; 9 | using Utils for Utils.Point; 10 | 11 | bytes32 public immutable gX; 12 | bytes32 public immutable gY; 13 | bytes32 public immutable hX; 14 | bytes32 public immutable hY; 15 | // above, emulating immutable `Utils.Point`s using raw `bytes32`s. save some sloads later. 16 | Utils.Point[M << 1] public gs; 17 | Utils.Point[M << 1] public hs; 18 | // have to use storage, not immutable, because solidity doesn't support non-primitive immutable types 19 | 20 | constructor() { 21 | Utils.Point memory gTemp = Utils.mapInto("g"); 22 | gX = gTemp.x; 23 | gY = gTemp.y; 24 | Utils.Point memory hTemp = Utils.mapInto("h"); 25 | hX = hTemp.x; 26 | hY = hTemp.y; 27 | for (uint256 i = 0; i < M << 1; i++) { 28 | gs[i] = Utils.mapInto("g", i); 29 | hs[i] = Utils.mapInto("h", i); 30 | } 31 | } 32 | 33 | struct Locals { 34 | uint256 o; 35 | Utils.Point P; 36 | uint256[m + 1] challenges; 37 | uint256[M << 1] s; 38 | } 39 | 40 | function verify(Utils.InnerProductStatement calldata statement, Utils.InnerProductProof calldata proof, bool transfer) external view { 41 | Locals memory locals; 42 | locals.o = statement.salt; 43 | locals.P = statement.P; 44 | uint256 M_ = M << (transfer ? 1 : 0); 45 | uint256 m_ = m + (transfer ? 1 : 0); 46 | 47 | for (uint256 i = 0; i < m_; i++) { 48 | locals.o = uint256(keccak256(abi.encode(locals.o, proof.L[i], proof.R[i]))).mod(); // overwrites 49 | locals.challenges[i] = locals.o; 50 | uint256 inverse = locals.o.inv(); 51 | locals.P = locals.P.add(proof.L[i].mul(locals.o.mul(locals.o))).add(proof.R[i].mul(inverse.mul(inverse))); 52 | } 53 | 54 | locals.s[0] = 1; 55 | for (uint256 i = 0; i < m_; i++) { 56 | locals.s[0] = locals.s[0].mul(locals.challenges[i]); 57 | } 58 | locals.s[0] = locals.s[0].inv(); 59 | for (uint256 i = 0; i < m_; i++) { 60 | for (uint256 j = 0; j < M_; j += 1 << m_ - i) { 61 | locals.s[j + (1 << m_ - i - 1)] = locals.s[j].mul(locals.challenges[i]).mul(locals.challenges[i]); 62 | } 63 | } 64 | 65 | Utils.Point memory temp = statement.u.mul(proof.a.mul(proof.b)); 66 | for (uint256 i = 0; i < M_; i++) { 67 | temp = temp.add(gs[i].mul(locals.s[i].mul(proof.a))); 68 | temp = temp.add(statement.hs[i].mul(locals.s[M_ - 1 - i].mul(proof.b))); 69 | } 70 | require(temp.eq(locals.P), "Inner product proof failed."); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/TransferVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./InnerProductVerifier.sol"; 6 | import "./Utils.sol"; 7 | 8 | contract TransferVerifier { 9 | using Utils for uint256; 10 | using Utils for Utils.Point; 11 | 12 | InnerProductVerifier immutable _ip; 13 | 14 | bytes32 immutable _gSumX; // 0x2fa4d012d8b2496ef27316c1447cd8958b034225a0fad7f9e9b944b7de8c5064 when Utils.m == 5 15 | bytes32 immutable _gSumY; // 0x0c648fe5b6fbbda8eec3d8ce13a891b005f4228f90638e84041b46a17bff0aae 16 | 17 | constructor(InnerProductVerifier ip_) { 18 | _ip = ip_; 19 | Utils.Point memory gSumTemp; 20 | for (uint256 i = 0; i < M << 1; i++) { 21 | gSumTemp = gSumTemp.add(gs(i)); 22 | } 23 | _gSumX = gSumTemp.x; 24 | _gSumY = gSumTemp.y; 25 | } 26 | 27 | function g() internal view returns (Utils.Point memory) { 28 | return Utils.Point(_ip.gX(), _ip.gY()); 29 | } 30 | 31 | function h() internal view returns (Utils.Point memory) { 32 | return Utils.Point(_ip.hX(), _ip.hY()); 33 | } 34 | 35 | function gs(uint256 i) internal view returns (Utils.Point memory) { 36 | (bytes32 x, bytes32 y) = _ip.gs(i); 37 | return Utils.Point(x, y); 38 | } 39 | 40 | function hs(uint256 i) internal view returns (Utils.Point memory) { 41 | (bytes32 x, bytes32 y) = _ip.hs(i); 42 | return Utils.Point(x, y); 43 | } 44 | 45 | function gSum() private view returns (Utils.Point memory) { 46 | return Utils.Point(_gSumX, _gSumY); 47 | } 48 | 49 | struct Locals { 50 | uint256 v; 51 | uint256 w; 52 | uint256 vPow; 53 | uint256 wPow; 54 | uint256[n][2][2] f; 55 | uint256[N][2] r; // each poly is an array of length N. evaluations of prods 56 | Utils.Point temp; 57 | Utils.Point CLnR; 58 | Utils.Point CRnR; 59 | Utils.Point CR; 60 | Utils.Point DR; 61 | Utils.Point yR; 62 | Utils.Point gR; 63 | Utils.Point C_XR; 64 | Utils.Point y_XR; 65 | uint256 y; 66 | uint256[M << 1] ys; 67 | uint256 z; 68 | uint256[2] zs; // [z^2, z^3] 69 | uint256[M << 1] twoTimesZSquared; 70 | uint256 zSum; 71 | uint256 x; 72 | uint256 t; 73 | uint256 k; 74 | Utils.Point tEval; 75 | uint256 c; 76 | Utils.Point A_y; 77 | Utils.Point A_D; 78 | Utils.Point A_b; 79 | Utils.Point A_X; 80 | Utils.Point A_t; 81 | Utils.Point gEpoch; 82 | Utils.Point A_u; 83 | } 84 | 85 | function verify(Utils.Statement calldata statement, Utils.TransferProof calldata proof) external view { 86 | Locals memory locals; 87 | locals.v = uint256(keccak256(abi.encode(statement.Y, statement.CLn, statement.CRn, statement.C, statement.D, statement.epoch, statement.fee, proof.BA, proof.BS, proof.A, proof.B))).mod(); 88 | locals.w = uint256(keccak256(abi.encode(locals.v, proof.CLnG, proof.CRnG, proof.C_0G, proof.DG, proof.y_0G, proof.gG, proof.C_XG, proof.y_XG))).mod(); 89 | for (uint256 row = 0; row < 2; row++) { 90 | for (uint256 k = 0; k < n; k++) { 91 | locals.f[row][1][k] = proof.f[row][k]; 92 | locals.f[row][0][k] = locals.w.sub(proof.f[row][k]); 93 | locals.temp = locals.temp.add(gs(k + n * row).mul(locals.f[row][1][k])); 94 | locals.temp = locals.temp.add(hs(k + n * row).mul(locals.f[row][1][k].mul(locals.f[row][0][k]))); 95 | } 96 | } 97 | 98 | require(proof.B.mul(locals.w).add(proof.A).eq(locals.temp.add(h().mul(proof.z_A))), "Bit-proof verification failed."); 99 | 100 | locals.r[0] = Utils.assemblePolynomials(locals.f[0]); 101 | locals.r[1] = Utils.assemblePolynomials(locals.f[1]); 102 | locals.wPow = 1; 103 | for (uint256 k = 0; k < n; k++) { 104 | uint256 wNeg = locals.wPow.neg(); 105 | locals.CLnR = locals.CLnR.add(proof.CLnG[k].mul(wNeg)); 106 | locals.CRnR = locals.CRnR.add(proof.CRnG[k].mul(wNeg)); 107 | locals.CR = locals.CR.add(proof.C_0G[k].mul(wNeg)); 108 | locals.DR = locals.DR.add(proof.DG[k].mul(wNeg)); 109 | locals.yR = locals.yR.add(proof.y_0G[k].mul(wNeg)); 110 | locals.gR = locals.gR.add(proof.gG[k].mul(wNeg)); 111 | locals.C_XR = locals.C_XR.add(proof.C_XG[k].mul(wNeg)); 112 | locals.y_XR = locals.y_XR.add(proof.y_XG[k].mul(wNeg)); 113 | 114 | locals.wPow = locals.wPow.mul(locals.w); 115 | } 116 | locals.vPow = locals.v; 117 | for (uint256 i = 0; i < N; i++) { 118 | locals.CLnR = locals.CLnR.add(statement.CLn[i].mul(locals.r[0][i])); 119 | locals.CRnR = locals.CRnR.add(statement.CRn[i].mul(locals.r[0][i])); 120 | locals.CR = locals.CR.add(statement.C[i].mul(locals.r[0][i])); 121 | locals.yR = locals.yR.add(statement.Y[i].mul(locals.r[0][i])); 122 | uint256 multiplier = locals.r[0][i].add(locals.r[1][i]); 123 | multiplier = multiplier.add(locals.vPow.mul(locals.wPow.sub(multiplier))); 124 | locals.C_XR = locals.C_XR.add(statement.C[i].mul(multiplier)); 125 | locals.y_XR = locals.y_XR.add(statement.Y[i].mul(multiplier)); 126 | 127 | locals.vPow = locals.vPow.mul(locals.v); // used to do this only if (i > 0) 128 | } 129 | locals.DR = locals.DR.add(statement.D.mul(locals.wPow)); 130 | locals.gR = locals.gR.add(g().mul(locals.wPow)); 131 | locals.C_XR = locals.C_XR.add(g().mul(statement.fee.mul(locals.wPow))); // this line is new 132 | 133 | locals.y = uint256(keccak256(abi.encode(locals.w))).mod(); 134 | locals.ys[0] = 1; 135 | locals.k = 1; 136 | for (uint256 i = 1; i < M << 1; i++) { 137 | locals.ys[i] = locals.ys[i - 1].mul(locals.y); 138 | locals.k = locals.k.add(locals.ys[i]); 139 | } 140 | locals.z = uint256(keccak256(abi.encode(locals.y))).mod(); 141 | locals.zs[0] = locals.z.mul(locals.z); 142 | locals.zs[1] = locals.zs[0].mul(locals.z); 143 | locals.zSum = locals.zs[0].add(locals.zs[1]).mul(locals.z); 144 | locals.k = locals.k.mul(locals.z.sub(locals.zs[0])).sub(locals.zSum.mul(1 << M).sub(locals.zSum)); 145 | locals.t = proof.tHat.sub(locals.k); // t = tHat - delta(y, z) 146 | for (uint256 i = 0; i < M; i++) { 147 | locals.twoTimesZSquared[i] = locals.zs[0].mul(1 << i); 148 | locals.twoTimesZSquared[i + M] = locals.zs[1].mul(1 << i); 149 | } 150 | 151 | locals.x = uint256(keccak256(abi.encode(locals.z, proof.T_1, proof.T_2))).mod(); 152 | locals.tEval = proof.T_1.mul(locals.x).add(proof.T_2.mul(locals.x.mul(locals.x))); // replace with "commit"? 153 | 154 | locals.A_y = locals.gR.mul(proof.s_sk).add(locals.yR.mul(proof.c.neg())); 155 | locals.A_D = g().mul(proof.s_r).add(statement.D.mul(proof.c.neg())); // add(mul(locals.gR, proof.s_r), mul(locals.DR, proof.c.neg())); 156 | locals.A_b = g().mul(proof.s_b).add(locals.DR.mul(locals.zs[0].neg()).add(locals.CRnR.mul(locals.zs[1])).mul(proof.s_sk).add(locals.CR.add(g().mul(statement.fee.mul(locals.wPow))).mul(locals.zs[0].neg()).add(locals.CLnR.mul(locals.zs[1])).mul(proof.c.neg()))); 157 | locals.A_X = locals.y_XR.mul(proof.s_r).add(locals.C_XR.mul(proof.c.neg())); 158 | locals.A_t = g().mul(locals.t).add(locals.tEval.neg()).mul(proof.c.mul(locals.wPow)).add(h().mul(proof.s_tau)).add(g().mul(proof.s_b.neg())); 159 | locals.gEpoch = Utils.mapInto("Firn Epoch", statement.epoch); // TODO: cast my own address to string as well? 160 | locals.A_u = locals.gEpoch.mul(proof.s_sk).add(statement.u.mul(proof.c.neg())); 161 | 162 | locals.c = uint256(keccak256(abi.encode(locals.x, locals.A_y, locals.A_D, locals.A_b, locals.A_X, locals.A_t, locals.A_u))).mod(); 163 | require(locals.c == proof.c, "Sigma protocol failure."); 164 | 165 | Utils.InnerProductStatement memory ip; // statement 166 | ip.salt = uint256(keccak256(abi.encode(locals.c))).mod(); 167 | ip.u = h().mul(ip.salt); 168 | ip.P = proof.BA.add(proof.BS.mul(locals.x)).add(gSum().mul(locals.z.neg())).add(h().mul(proof.mu.neg())).add(ip.u.mul(proof.tHat)); 169 | for (uint256 i = 0; i < M << 1; i++) { 170 | ip.hs[i] = hs(i).mul(locals.ys[i].inv()); 171 | ip.P = ip.P.add(ip.hs[i].mul(locals.ys[i].mul(locals.z).add(locals.twoTimesZSquared[i]))); 172 | } 173 | 174 | _ip.verify(ip, proof.ip, true); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Treasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./ERC20.sol"; 6 | 7 | contract Treasury { 8 | ERC20 immutable _erc20; 9 | uint256 _firnSupply; 10 | uint256 constant _startGas = 10000000; // prevent low-gas attack (only get top elements) 11 | uint256 constant _endGas = 100000; // hardcode to save sloads... 12 | 13 | event Payout(address indexed recipient, uint256 amount); 14 | 15 | constructor(ERC20 erc20_) { 16 | _erc20 = erc20_; 17 | } 18 | 19 | receive() external payable {} 20 | 21 | function payout() external { 22 | require(gasleft() >= _startGas, "Not enough gas supplied."); 23 | _firnSupply = _erc20.totalSupply(); 24 | traverse(_erc20.root()); 25 | } 26 | 27 | function traverse(address cursor) internal { 28 | (, address left, address right,) = _erc20.nodes(cursor); 29 | 30 | if (right != address(0)) { 31 | traverse(right); 32 | } 33 | if (gasleft() < _endGas) { 34 | return; 35 | } 36 | uint256 firnBalance = _erc20.balanceOf(cursor); 37 | uint256 amount = address(this).balance * firnBalance / _firnSupply; 38 | (bool success,) = payable(cursor).call{gas: 40000, value: amount}(""); // enough gas to pay gnosis safe 39 | if (success) { 40 | emit Payout(cursor, amount); 41 | } 42 | // there is a further attack where someone could try to transfer their own firn balance within their `receive`. 43 | // the effect of this would be to get paid essentially twice for the same firn (there are other variants of this). 44 | // to prevent this, we're assuming that 2,300 gas isn't enough to do a FIRN ERC20 transfer. 45 | _firnSupply -= firnBalance; 46 | if (left != address(0)) { 47 | traverse(left); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | uint256 constant n = 4; 6 | uint256 constant N = 1 << n; 7 | uint256 constant m = 5; 8 | uint256 constant M = 1 << m; 9 | 10 | library Utils { 11 | uint256 constant GROUP_ORDER = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; 12 | uint256 constant FIELD_ORDER = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; 13 | uint256 constant PPLUS1DIV4 = 0x0c19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52; 14 | 15 | function add(uint256 x, uint256 y) internal pure returns (uint256) { 16 | return addmod(x, y, GROUP_ORDER); 17 | } 18 | 19 | function mul(uint256 x, uint256 y) internal pure returns (uint256) { 20 | return mulmod(x, y, GROUP_ORDER); 21 | } 22 | 23 | function inv(uint256 x) internal view returns (uint256) { 24 | return exp(x, GROUP_ORDER - 2); 25 | } 26 | 27 | function mod(uint256 x) internal pure returns (uint256) { 28 | return x % GROUP_ORDER; 29 | } 30 | 31 | function sub(uint256 x, uint256 y) internal pure returns (uint256) { 32 | return x >= y ? x - y : GROUP_ORDER - y + x; 33 | } 34 | 35 | function neg(uint256 x) internal pure returns (uint256) { 36 | return GROUP_ORDER - x; 37 | } 38 | 39 | function exp(uint256 base, uint256 exponent) internal view returns (uint256 output) { 40 | uint256 order = GROUP_ORDER; 41 | assembly { 42 | let location := mload(0x40) 43 | mstore(location, 0x20) 44 | mstore(add(location, 0x20), 0x20) 45 | mstore(add(location, 0x40), 0x20) 46 | mstore(add(location, 0x60), base) 47 | mstore(add(location, 0x80), exponent) 48 | mstore(add(location, 0xa0), order) 49 | if iszero(staticcall(gas(), 0x05, location, 0xc0, location, 0x20)) { revert(0, 0) } 50 | output := mload(location) 51 | } 52 | } 53 | 54 | function fieldExp(uint256 base, uint256 exponent) internal view returns (uint256 output) { 55 | // warning: mod p, not q 56 | uint256 order = FIELD_ORDER; 57 | assembly { 58 | let location := mload(0x40) 59 | mstore(location, 0x20) 60 | mstore(add(location, 0x20), 0x20) 61 | mstore(add(location, 0x40), 0x20) 62 | mstore(add(location, 0x60), base) 63 | mstore(add(location, 0x80), exponent) 64 | mstore(add(location, 0xa0), order) 65 | if iszero(staticcall(gas(), 0x05, location, 0xc0, location, 0x20)) { revert(0, 0) } 66 | output := mload(location) 67 | } 68 | } 69 | 70 | struct Point { 71 | bytes32 x; 72 | bytes32 y; 73 | } 74 | 75 | function add(Point memory p1, Point memory p2) internal view returns (Point memory r) { 76 | assembly { 77 | let location := mload(0x40) 78 | mstore(location, mload(p1)) 79 | mstore(add(location, 0x20), mload(add(p1, 0x20))) 80 | mstore(add(location, 0x40), mload(p2)) 81 | mstore(add(location, 0x60), mload(add(p2, 0x20))) 82 | if iszero(staticcall(gas(), 0x06, location, 0x80, r, 0x40)) { revert(0, 0) } 83 | } 84 | } 85 | 86 | function mul(Point memory p, uint256 s) internal view returns (Point memory r) { 87 | assembly { 88 | let location := mload(0x40) 89 | mstore(location, mload(p)) 90 | mstore(add(location, 0x20), mload(add(p, 0x20))) 91 | mstore(add(location, 0x40), s) 92 | if iszero(staticcall(gas(), 0x07, location, 0x60, r, 0x40)) { revert(0, 0) } 93 | } 94 | } 95 | 96 | function neg(Point memory p) internal pure returns (Point memory) { 97 | return Point(p.x, bytes32(FIELD_ORDER - uint256(p.y))); // p.y should already be reduced mod P? 98 | } 99 | 100 | function eq(Point memory p1, Point memory p2) internal pure returns (bool) { 101 | return p1.x == p2.x && p1.y == p2.y; 102 | } 103 | 104 | function decompress(bytes32 input) internal view returns (Point memory) { 105 | if (input == 0x00) return Point(0x00, 0x00); 106 | uint256 x = uint256(input); 107 | uint256 sign = (x & 0x8000000000000000000000000000000000000000000000000000000000000000) >> 255; 108 | x &= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 109 | uint256 ySquared = fieldExp(x, 3) + 3; 110 | uint256 y = fieldExp(ySquared, PPLUS1DIV4); 111 | Point memory result = Point(bytes32(x), bytes32(y)); 112 | if (sign != y & 0x01) return neg(result); 113 | return result; 114 | } 115 | 116 | function compress(Point memory input) internal pure returns (bytes32) { 117 | uint256 result = uint256(input.x); 118 | if (uint256(input.y) & 0x01 == 0x01) result |= 0x8000000000000000000000000000000000000000000000000000000000000000; 119 | return bytes32(result); 120 | } 121 | 122 | function mapInto(uint256 seed) internal view returns (Point memory) { 123 | uint256 y; 124 | while (true) { 125 | uint256 ySquared = fieldExp(seed, 3) + 3; // addmod instead of add: waste of gas, plus function overhead cost 126 | y = fieldExp(ySquared, PPLUS1DIV4); 127 | if (fieldExp(y, 2) == ySquared) { 128 | break; 129 | } 130 | seed += 1; 131 | } 132 | return Point(bytes32(seed), bytes32(y)); 133 | } 134 | 135 | function mapInto(string memory input) internal view returns (Point memory) { 136 | return mapInto(uint256(keccak256(abi.encodePacked(input))) % FIELD_ORDER); 137 | } 138 | 139 | function mapInto(string memory input, uint256 i) internal view returns (Point memory) { 140 | return mapInto(uint256(keccak256(abi.encodePacked(input, i))) % FIELD_ORDER); 141 | } 142 | 143 | function slice(bytes memory input, uint256 start) internal pure returns (bytes32 result) { 144 | assembly { 145 | result := mload(add(add(input, 0x20), start)) 146 | } 147 | } 148 | 149 | struct Statement { 150 | Point[N] Y; 151 | Point[N] CLn; 152 | Point[N] CRn; 153 | Point[N] C; 154 | Point D; 155 | uint256 epoch; 156 | Point u; 157 | uint256 fee; 158 | } 159 | 160 | struct DepositProof { 161 | Point A; 162 | Point B; 163 | Point[n] C_XG; 164 | Point[n] y_XG; 165 | uint256[n] f; 166 | uint256 z_A; 167 | uint256 c; 168 | uint256 s_r; 169 | } 170 | 171 | function deserializeDeposit(bytes memory arr) internal view returns (DepositProof memory proof) { 172 | proof.A = decompress(slice(arr, 0)); 173 | proof.B = decompress(slice(arr, 32)); 174 | 175 | for (uint256 k = 0; k < n; k++) { 176 | proof.C_XG[k] = decompress(slice(arr, 64 + k * 32)); 177 | proof.y_XG[k] = decompress(slice(arr, 64 + (k + n) * 32)); 178 | proof.f[k] = uint256(slice(arr, 64 + n * 64 + k * 32)); 179 | } 180 | uint256 starting = n * 96; 181 | proof.z_A = uint256(slice(arr, 64 + starting)); 182 | 183 | proof.c = uint256(slice(arr, 96 + starting)); 184 | proof.s_r = uint256(slice(arr, 128 + starting)); 185 | 186 | return proof; 187 | } 188 | 189 | struct TransferProof { 190 | Point BA; 191 | Point BS; 192 | Point A; 193 | Point B; 194 | Point[n] CLnG; 195 | Point[n] CRnG; 196 | Point[n] C_0G; 197 | Point[n] DG; 198 | Point[n] y_0G; 199 | Point[n] gG; 200 | Point[n] C_XG; 201 | Point[n] y_XG; 202 | uint256[n][2] f; 203 | uint256 z_A; 204 | Point T_1; 205 | Point T_2; 206 | uint256 tHat; 207 | uint256 mu; 208 | uint256 c; 209 | uint256 s_sk; 210 | uint256 s_r; 211 | uint256 s_b; 212 | uint256 s_tau; 213 | InnerProductProof ip; 214 | } 215 | 216 | function deserializeTransfer(bytes memory arr) internal view returns (TransferProof memory proof) { 217 | proof.BA = decompress(slice(arr, 0)); 218 | proof.BS = decompress(slice(arr, 32)); 219 | proof.A = decompress(slice(arr, 64)); 220 | proof.B = decompress(slice(arr, 96)); 221 | 222 | for (uint256 k = 0; k < n; k++) { 223 | proof.CLnG[k] = decompress(slice(arr, 128 + k * 32)); 224 | proof.CRnG[k] = decompress(slice(arr, 128 + (k + n) * 32)); 225 | proof.C_0G[k] = decompress(slice(arr, 128 + n * 64 + k * 32)); 226 | proof.DG[k] = decompress(slice(arr, 128 + n * 96 + k * 32)); 227 | proof.y_0G[k] = decompress(slice(arr, 128 + n * 128 + k * 32)); 228 | proof.gG[k] = decompress(slice(arr, 128 + n * 160 + k * 32)); 229 | proof.C_XG[k] = decompress(slice(arr, 128 + n * 192 + k * 32)); 230 | proof.y_XG[k] = decompress(slice(arr, 128 + n * 224 + k * 32)); 231 | proof.f[0][k] = uint256(slice(arr, 128 + n * 256 + k * 32)); 232 | proof.f[1][k] = uint256(slice(arr, 128 + n * 288 + k * 32)); 233 | } 234 | 235 | uint256 starting = n * 320; 236 | proof.z_A = uint256(slice(arr, 128 + starting)); 237 | 238 | proof.T_1 = decompress(slice(arr, 160 + starting)); 239 | proof.T_2 = decompress(slice(arr, 192 + starting)); 240 | proof.tHat = uint256(slice(arr, 224 + starting)); 241 | proof.mu = uint256(slice(arr, 256 + starting)); 242 | 243 | proof.c = uint256(slice(arr, 288 + starting)); 244 | proof.s_sk = uint256(slice(arr, 320 + starting)); 245 | proof.s_r = uint256(slice(arr, 352 + starting)); 246 | proof.s_b = uint256(slice(arr, 384 + starting)); 247 | proof.s_tau = uint256(slice(arr, 416 + starting)); 248 | 249 | for (uint256 i = 0; i < m + 1; i++) { 250 | proof.ip.L[i] = decompress(slice(arr, 448 + starting + i * 32)); 251 | proof.ip.R[i] = decompress(slice(arr, 448 + starting + (i + m + 1) * 32)); 252 | } 253 | proof.ip.a = uint256(slice(arr, 448 + starting + (m + 1) * 64)); 254 | proof.ip.b = uint256(slice(arr, 480 + starting + (m + 1) * 64)); 255 | 256 | return proof; 257 | } 258 | 259 | struct WithdrawalProof { 260 | Point BA; 261 | Point BS; 262 | Point A; 263 | Point B; 264 | Point[n] CLnG; 265 | Point[n] CRnG; 266 | Point[n] y_0G; 267 | Point[n] gG; 268 | Point[n] C_XG; 269 | Point[n] y_XG; 270 | uint256[n] f; 271 | uint256 z_A; 272 | Point T_1; 273 | Point T_2; 274 | uint256 tHat; 275 | uint256 mu; 276 | uint256 c; 277 | uint256 s_sk; 278 | uint256 s_r; 279 | uint256 s_b; 280 | uint256 s_tau; 281 | InnerProductProof ip; 282 | } 283 | 284 | function deserializeWithdrawal(bytes memory arr) internal view returns (WithdrawalProof memory proof) { 285 | proof.BA = decompress(slice(arr, 0)); 286 | proof.BS = decompress(slice(arr, 32)); 287 | proof.A = decompress(slice(arr, 64)); 288 | proof.B = decompress(slice(arr, 96)); 289 | 290 | for (uint256 k = 0; k < n; k++) { 291 | proof.CLnG[k] = decompress(slice(arr, 128 + k * 32)); 292 | proof.CRnG[k] = decompress(slice(arr, 128 + (k + n) * 32)); 293 | proof.y_0G[k] = decompress(slice(arr, 128 + n * 64 + k * 32)); 294 | proof.gG[k] = decompress(slice(arr, 128 + n * 96 + k * 32)); 295 | proof.C_XG[k] = decompress(slice(arr, 128 + n * 128 + k * 32)); 296 | proof.y_XG[k] = decompress(slice(arr, 128 + n * 160 + k * 32)); 297 | proof.f[k] = uint256(slice(arr, 128 + n * 192 + k * 32)); 298 | } 299 | uint256 starting = n * 224; 300 | proof.z_A = uint256(slice(arr, 128 + starting)); 301 | 302 | proof.T_1 = decompress(slice(arr, 160 + starting)); 303 | proof.T_2 = decompress(slice(arr, 192 + starting)); 304 | proof.tHat = uint256(slice(arr, 224 + starting)); 305 | proof.mu = uint256(slice(arr, 256 + starting)); 306 | 307 | proof.c = uint256(slice(arr, 288 + starting)); 308 | proof.s_sk = uint256(slice(arr, 320 + starting)); 309 | proof.s_r = uint256(slice(arr, 352 + starting)); 310 | proof.s_b = uint256(slice(arr, 384 + starting)); 311 | proof.s_tau = uint256(slice(arr, 416 + starting)); 312 | 313 | for (uint256 i = 0; i < m; i++) { 314 | // will leave the `m`th element empty 315 | proof.ip.L[i] = decompress(slice(arr, 448 + starting + i * 32)); 316 | proof.ip.R[i] = decompress(slice(arr, 448 + starting + (i + m) * 32)); 317 | } 318 | proof.ip.a = uint256(slice(arr, 448 + starting + m * 64)); 319 | proof.ip.b = uint256(slice(arr, 480 + starting + m * 64)); 320 | 321 | return proof; 322 | } 323 | 324 | struct InnerProductStatement { 325 | uint256 salt; 326 | Point[M << 1] hs; // "overridden" parameters. 327 | Point u; 328 | Point P; 329 | } 330 | 331 | struct InnerProductProof { 332 | Point[m + 1] L; 333 | Point[m + 1] R; 334 | uint256 a; 335 | uint256 b; 336 | } 337 | 338 | function assemblePolynomials(uint256[n][2] memory f) internal pure returns (uint256[N] memory result) { 339 | // f is a 2m-by-2 array... containing the f's and x - f's, twice (i.e., concatenated). 340 | // output contains two "rows", each of length N. 341 | result[0] = 1; 342 | for (uint256 k = 0; k < n; k++) { 343 | for (uint256 i = 0; i < N; i += 1 << n - k) { 344 | result[i + (1 << n - 1 - k)] = mul(result[i], f[1][n - 1 - k]); 345 | result[i] = mul(result[i], f[0][n - 1 - k]); 346 | } 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/WithdrawalVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./InnerProductVerifier.sol"; 6 | import "./Utils.sol"; 7 | 8 | contract WithdrawalVerifier { 9 | using Utils for uint256; 10 | using Utils for Utils.Point; 11 | 12 | InnerProductVerifier immutable _ip; 13 | 14 | bytes32 immutable _gSumX; // 0x1bcf9024624aef47656cdbd47d104a1b30efac20504e72d395e7e012727c73a3 when Utils.m == 5 15 | bytes32 immutable _gSumY; // 0x052d5b8798a0be8c27d47246f021c2e9841837904a92a33dc4f6c755fda097bd 16 | 17 | constructor(InnerProductVerifier ip_) { 18 | _ip = ip_; 19 | Utils.Point memory gSumTemp; 20 | for (uint256 i = 0; i < M; i++) { 21 | gSumTemp = gSumTemp.add(gs(i)); 22 | } 23 | _gSumX = gSumTemp.x; 24 | _gSumY = gSumTemp.y; 25 | } 26 | 27 | function g() internal view returns (Utils.Point memory) { 28 | return Utils.Point(_ip.gX(), _ip.gY()); 29 | } 30 | 31 | function h() internal view returns (Utils.Point memory) { 32 | return Utils.Point(_ip.hX(), _ip.hY()); 33 | } 34 | 35 | function gs(uint256 i) internal view returns (Utils.Point memory) { 36 | (bytes32 x, bytes32 y) = _ip.gs(i); 37 | return Utils.Point(x, y); 38 | } 39 | 40 | function hs(uint256 i) internal view returns (Utils.Point memory) { 41 | (bytes32 x, bytes32 y) = _ip.hs(i); 42 | return Utils.Point(x, y); 43 | } 44 | 45 | function gSum() private view returns (Utils.Point memory) { 46 | return Utils.Point(_gSumX, _gSumY); 47 | } 48 | 49 | struct Locals { 50 | uint256 v; 51 | uint256 w; 52 | uint256 vPow; 53 | uint256 wPow; 54 | uint256[n][2] f; // could just allocate extra space in the proof? 55 | uint256[N] r; // each poly is an array of length N. evaluations of prods 56 | Utils.Point temp; 57 | Utils.Point CLnR; 58 | Utils.Point CRnR; 59 | Utils.Point yR; 60 | Utils.Point gR; 61 | Utils.Point C_XR; 62 | Utils.Point y_XR; 63 | uint256 y; 64 | uint256[M] ys; 65 | uint256 z; 66 | uint256[1] zs; // silly. just to match zether. 67 | uint256[M] twoTimesZSquared; 68 | uint256 zSum; 69 | uint256 x; 70 | uint256 t; 71 | uint256 k; 72 | Utils.Point tEval; 73 | uint256 c; 74 | Utils.Point A_y; 75 | Utils.Point A_D; 76 | Utils.Point A_b; 77 | Utils.Point A_X; 78 | Utils.Point A_t; 79 | Utils.Point gEpoch; 80 | Utils.Point A_u; 81 | } 82 | 83 | function verify(uint256 amount, Utils.Statement calldata statement, Utils.WithdrawalProof calldata proof, uint256 salt) external view { 84 | Locals memory locals; 85 | locals.v = uint256(keccak256(abi.encode(salt, amount, statement.Y, statement.CLn, statement.CRn, statement.C, statement.D, statement.epoch, statement.fee, proof.BA, proof.BS, proof.A, proof.B))).mod(); 86 | locals.w = uint256(keccak256(abi.encode(locals.v, proof.CLnG, proof.CRnG, proof.y_0G, proof.gG, proof.C_XG, proof.y_XG))).mod(); 87 | for (uint256 k = 0; k < n; k++) { 88 | locals.f[1][k] = proof.f[k]; 89 | locals.f[0][k] = locals.w.sub(proof.f[k]); 90 | locals.temp = locals.temp.add(gs(k).mul(locals.f[1][k])); 91 | locals.temp = locals.temp.add(hs(k).mul(locals.f[1][k].mul(locals.f[0][k]))); 92 | } 93 | require(proof.B.mul(locals.w).add(proof.A).eq(locals.temp.add(h().mul(proof.z_A))), "Bit-proof verification failed."); 94 | 95 | locals.r = Utils.assemblePolynomials(locals.f); 96 | locals.wPow = 1; 97 | for (uint256 k = 0; k < n; k++) { 98 | locals.CLnR = locals.CLnR.add(proof.CLnG[k].mul(locals.wPow.neg())); 99 | locals.CRnR = locals.CRnR.add(proof.CRnG[k].mul(locals.wPow.neg())); 100 | locals.yR = locals.yR.add(proof.y_0G[k].mul(locals.wPow.neg())); 101 | locals.gR = locals.gR.add(proof.gG[k].mul(locals.wPow.neg())); 102 | locals.C_XR = locals.C_XR.add(proof.C_XG[k].mul(locals.wPow.neg())); 103 | locals.y_XR = locals.y_XR.add(proof.y_XG[k].mul(locals.wPow.neg())); 104 | 105 | locals.wPow = locals.wPow.mul(locals.w); 106 | } 107 | locals.vPow = locals.v; // used to be 1 108 | for (uint256 i = 0; i < N; i++) { 109 | locals.CLnR = locals.CLnR.add(statement.CLn[i].mul(locals.r[i])); 110 | locals.CRnR = locals.CRnR.add(statement.CRn[i].mul(locals.r[i])); 111 | locals.yR = locals.yR.add(statement.Y[i].mul(locals.r[i])); 112 | uint256 multiplier = locals.r[i].add(locals.vPow.mul(locals.wPow.sub(locals.r[i]))); // locals. ? 113 | locals.C_XR = locals.C_XR.add(statement.C[i].mul(multiplier)); 114 | locals.y_XR = locals.y_XR.add(statement.Y[i].mul(multiplier)); 115 | locals.vPow = locals.vPow.mul(locals.v); // used to do this only if (i > 0) 116 | } 117 | locals.gR = locals.gR.add(g().mul(locals.wPow)); 118 | locals.C_XR = locals.C_XR.add(g().mul(statement.fee.add(amount).mul(locals.wPow))); // this line is new 119 | 120 | locals.y = uint256(keccak256(abi.encode(locals.w))).mod(); 121 | locals.ys[0] = 1; 122 | locals.k = 1; 123 | for (uint256 i = 1; i < M; i++) { 124 | locals.ys[i] = locals.ys[i - 1].mul(locals.y); 125 | locals.k = locals.k.add(locals.ys[i]); 126 | } 127 | locals.z = uint256(keccak256(abi.encode(locals.y))).mod(); 128 | locals.zs[0] = locals.z.mul(locals.z); 129 | locals.zSum = locals.zs[0].mul(locals.z); // trivial sum 130 | locals.k = locals.k.mul(locals.z.sub(locals.zs[0])).sub(locals.zSum.mul(1 << M).sub(locals.zSum)); 131 | locals.t = proof.tHat.sub(locals.k); 132 | for (uint256 i = 0; i < M; i++) { 133 | locals.twoTimesZSquared[i] = locals.zs[0].mul(1 << i); 134 | } 135 | 136 | locals.x = uint256(keccak256(abi.encode(locals.z, proof.T_1, proof.T_2))).mod(); 137 | locals.tEval = proof.T_1.mul(locals.x).add(proof.T_2.mul(locals.x.mul(locals.x))); // replace with "commit"? 138 | 139 | locals.A_y = locals.gR.mul(proof.s_sk).add(locals.yR.mul(proof.c.neg())); 140 | locals.A_D = g().mul(proof.s_r).add(statement.D.mul(proof.c.neg())); // add(mul(locals.gR, proof.s_r), mul(locals.DR, proof.c.neg())); 141 | locals.A_b = g().mul(proof.s_b).add(locals.CRnR.mul(locals.zs[0]).mul(proof.s_sk).add(locals.CLnR.mul(locals.zs[0]).mul(proof.c.neg()))); 142 | locals.A_X = locals.y_XR.mul(proof.s_r).add(locals.C_XR.mul(proof.c.neg())); 143 | locals.A_t = g().mul(locals.t).add(locals.tEval.neg()).mul(proof.c.mul(locals.wPow)).add(h().mul(proof.s_tau)).add(g().mul(proof.s_b.neg())); 144 | locals.gEpoch = Utils.mapInto("Firn Epoch", statement.epoch); // TODO: cast my own address to string as well? 145 | locals.A_u = locals.gEpoch.mul(proof.s_sk).add(statement.u.mul(proof.c.neg())); 146 | 147 | locals.c = uint256(keccak256(abi.encode(locals.x, locals.A_y, locals.A_D, locals.A_b, locals.A_X, locals.A_t, locals.A_u))).mod(); 148 | require(locals.c == proof.c, "Sigma protocol failure."); 149 | 150 | Utils.InnerProductStatement memory ip; // statement 151 | ip.salt = uint256(keccak256(abi.encode(locals.c))).mod(); 152 | ip.u = h().mul(ip.salt); 153 | ip.P = proof.BA.add(proof.BS.mul(locals.x)).add(gSum().mul(locals.z.neg())).add(h().mul(proof.mu.neg())).add(ip.u.mul(proof.tHat)); 154 | for (uint256 i = 0; i < M; i++) { 155 | ip.hs[i] = hs(i).mul(locals.ys[i].inv()); 156 | ip.P = ip.P.add(ip.hs[i].mul(locals.ys[i].mul(locals.z).add(locals.twoTimesZSquared[i]))); 157 | } 158 | 159 | _ip.verify(ip, proof.ip, false); 160 | } 161 | } 162 | --------------------------------------------------------------------------------