├── .gitignore ├── LICENSE ├── README.md ├── docs ├── AnonZether.pdf └── Infrastructure.pdf ├── lerna.json ├── package.json ├── packages ├── anonymous.js │ ├── .jsdoc.json │ ├── package.json │ └── src │ │ ├── client.js │ │ ├── prover │ │ ├── burn.js │ │ ├── innerproduct.js │ │ └── zether.js │ │ └── utils │ │ ├── algebra.js │ │ ├── bn128.js │ │ ├── misc.js │ │ ├── service.js │ │ └── utils.js └── protocol │ ├── contracts │ ├── BurnVerifier.sol │ ├── CashToken.sol │ ├── InnerProductVerifier.sol │ ├── Migrations.sol │ ├── Utils.sol │ ├── ZSC.sol │ └── ZetherVerifier.sol │ ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_zsc.js │ ├── package.json │ ├── test │ └── zsc.js │ └── truffle-config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | package-lock.json 4 | yarn-error.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 JP Morgan Chase Company 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Anonymous Zether 2 | 3 | _**This repo is not actively maintained by Consensys. It is Apache 2.0 licensed.**_ 4 | 5 | This is a private payment system, an _anonymous_ extension of Bünz, Agrawal, Zamani and Boneh's [Zether protocol](https://eprint.iacr.org/2019/191.pdf). 6 | 7 | The authors sketch an anonymous extension in their original manuscript. We develop an explicit proof protocol for this extension, described in the technical paper [AnonZether.pdf](docs/AnonZether.pdf). We also fully implement this anonymous protocol (including verification contracts and a client / front-end with its own proof generator). 8 | 9 | ## High-level overview 10 | 11 | Anonymous Zether is an private value-tracking system, in which an Ethereum smart contract maintains encrypted account balances. Each Zether Smart Contract (ZSC) must, upon deployment, "attach" to some already-deployed ERC-20 contract; once deployed, this contract establishes special "Zether" accounts into / out of which users may _deposit_ or _withdraw_ ERC-20 funds. Having credited funds to a Zether account, its owner may privately send these funds to other Zether accounts, _confidentially_ (transferred amounts are private) and _anonymously_ (identities of transactors are private). Only the owner of each account's secret key may spend its funds, and overdraws are impossible. 12 | 13 | To enhance their privacy, users should conduct as much business as possible within the ZSC. 14 | 15 | The (anonymous) Zether Smart Contract operates roughly as follows (see the [original Zether paper](https://eprint.iacr.org/2019/191.pdf) for more details). Each account consists of an ElGamal ciphertext, which encrypts the account's balance under its own public key. To send funds, Alice publishes an ordered list of public keys—which contains herself and the recipient, among other arbitrarily chosen parties—together with a corresponding list of ElGamal ciphertexts, which respectively encrypt (under the appropriate public keys) the amounts by which Alice intends to adjust these various accounts' balances. The ZSC applies these adjustments using the homomomorphic property of ElGamal encryption (with "message in the exponent"). Alice finally publishes a zero-knowledge proof which asserts that she knows her own secret key, that she owns enough to cover her deduction, that she deducted funds only from herself, and credited them only to Bob (and by the same amount she debited, no less); she of course also demonstrates that she did not alter those balances other than her own and Bob's. These adjustment ciphertexts—opaque to any outside observer—conceal who sent funds to whom, and how much was sent. 16 | 17 | Users need _never_ interact directly with the ZSC; rather, our front-end client streamlines its use. 18 | 19 | Our theoretical contribution is a zero-knowledge proof protocol for the anonymous transfer statement (8) of [Bünz, et al.](https://eprint.iacr.org/2019/191.pdf), which moreover has appealing asymptotic performance characteristics; details on our techniques can be found in our [paper](docs/AnonZether.pdf). We also of course provide this implementation. 20 | 21 | ## Prerequisites 22 | 23 | Anonymous Zether can be deployed and tested easily using [Truffle](https://www.trufflesuite.com/truffle) and [Ganache](https://www.trufflesuite.com/ganache). 24 | 25 | ### Required utilities 26 | * [Yarn](https://yarnpkg.com/en/docs/install#mac-stable), tested with version v1.22.19. 27 | * [Node.js](https://nodejs.org/en/download/), tested with version v16.15.1. 28 | 29 | Run the following commands: 30 | ```bash 31 | npm install -g truffle 32 | npm install -g ganache 33 | ``` 34 | **Note:** last tested with truffle v5.5.27 and ganache v7.3.2. 35 | 36 | In the main directory, type `yarn`. 37 | 38 | ## Running Tests 39 | 40 | Navigate to the [protocol](./packages/protocol) directory. Type `yarn`. 41 | 42 | Now, in one window, type 43 | ```bash 44 | ganache-cli --gasPrice 0 -k berlin 45 | ``` 46 | **Note:** the `-k berlin` fork is required due to an externally signed transaction (by a random account with no balance) which is part of the transfer flow. The london fork introduces minimum pricing for gas. 47 | 48 | In a second window, type 49 | ```bash 50 | truffle test 51 | ``` 52 | This command should compile and deploy all necessary contracts, as well as run some example code. You can see this example code in the test file [zsc.js](./packages/protocol/test/zsc.js). 53 | 54 | ## Detailed usage example 55 | In case you haven't already, navigate to `packages/protocol` directory and run `truffle migrate` to compile the contracts. 56 | 57 | In a `node` console, set the variable `__dirname` so that it points to the `packages/protocol` directory. Initialize `web3` in the following way: 58 | ```javascript 59 | > Web3 = require('web3'); 60 | > const web3 = new Web3('http://localhost:8545'); 61 | > const provider = new Web3.providers.WebsocketProvider("ws://localhost:8545"); 62 | ``` 63 | Now, import `Client`: 64 | ```javascript 65 | > const Client = require(path.join(__dirname, '../anonymous.js/src/client.js')); 66 | ``` 67 | 68 | The contracts `ZSC` and `CashToken` should be imported in `node` using Truffle's `@truffle/contract` objects: 69 | ```javascript 70 | > contract = require("@truffle/contract"); 71 | > path = require('path'); 72 | > const ZSCJSON = require(path.join(__dirname, 'build/contracts/ZSC.json')); 73 | > const ZSC = contract(ZSCJSON); 74 | > ZSC.setProvider(provider); 75 | > ZSC.deployed(); 76 | > let zsc; 77 | > ZSC.at(ZSC.address).then((result) => { zsc = result; }); 78 | ``` 79 | Following the example above, import CashToken: 80 | ```javascript 81 | > CashTokenJSON = require(path.join(__dirname, 'build/contracts/CashToken.json')); 82 | > const CashToken = contract(CashTokenJSON); 83 | > CashToken.setProvider(provider); 84 | > CashToken.deployed(); 85 | > let cash; 86 | > CashToken.at(CashToken.address).then((result) => { cash = result; }); 87 | ``` 88 | Before proceeding, you should mint yourself some funds. An example is shown below: 89 | ```javascript 90 | > cash.mint(home, 1000, { 'from': home }); 91 | > cash.approve(zsc.address, 1000, { 'from': home }); 92 | ``` 93 | Let's assume now that, in four separate `node` consoles, you've imported `Client` and initialized `web3` to an appropriate provider in this way. In each window, type: 94 | ```javascript 95 | > let home; 96 | > web3.eth.getAccounts().then((accounts) => { home = accounts[accounts.length - 1]; }); 97 | ``` 98 | to assign the address of an unlocked account to the variable `home`. 99 | 100 | In the first window, Alice's let's say, execute: 101 | ```javascript 102 | > const alice = new Client(web3, zsc.contract, home); 103 | > alice.register(); 104 | Promise { } 105 | Registration submitted (txHash = "0xe4295b5116eb1fe6c40a40352f4bf609041f93e763b5b27bd28c866f3f4ce2b2"). 106 | Registration successful. 107 | ``` 108 | and in Bob's, 109 | ```javascript 110 | > const bob = new Client(web3, zsc.contract, home); 111 | > bob.register(); 112 | ``` 113 | 114 | The two functions `deposit` and `withdraw` take a single numerical parameter. For example, in Alice's window, type: 115 | ```javascript 116 | > alice.deposit(100); 117 | Initiating deposit. 118 | Promise { } 119 | Deposit submitted (txHash = "0xa6e4c2d415dda9402c6b20a6b1f374939f847c00d7c0f206200142597ff5be7e"). 120 | Deposit of 100 was successful. Balance now 100. 121 | ``` 122 | Now, type: 123 | ```javascript 124 | > alice.withdraw(10); 125 | Initiating withdrawal. 126 | Promise { } 127 | Withdrawal submitted (txHash = "0xd7cd5de1680594da89823a18c0b74716b6953e23fe60056cc074df75e94c92c5"). 128 | Withdrawal of 10 was successful. Balance now 90. 129 | ``` 130 | In Bob's window, use 131 | ```javascript 132 | > bob.account.public(); 133 | [ 134 | '0x17f5f0daab7218a462dea5f04d47c9db95497833381f502560486d4019aec495', 135 | '0x0957b7a0ec24a779f991ea645366b27fe3e93e996f3e7936bdcfb7b18d41a945' 136 | ] 137 | ``` 138 | to retrieve his public key and add Bob as a "friend" of Alice, i.e. 139 | ```javascript 140 | > alice.friends.add("Bob", ['0x17f5f0daab7218a462dea5f04d47c9db95497833381f502560486d4019aec495', '0x0957b7a0ec24a779f991ea645366b27fe3e93e996f3e7936bdcfb7b18d41a945']); 141 | 'Friend added.' 142 | ``` 143 | You can now do: 144 | ```javascript 145 | > alice.transfer("Bob", 20); 146 | Initiating transfer. 147 | Promise { } 148 | Transfer submitted (txHash = "0x4c0631483e6ea89d2068c90d5a2f9fa42ad12a102650ff80b887542e18e1d988"). 149 | Transfer of 20 was successful. Balance now 70. 150 | ``` 151 | In Bob's window, you should see: 152 | ```javascript 153 | Transfer of 20 received! Balance now 20. 154 | ``` 155 | You can also add Alice, Carol and Dave as friends of Bob. Now, you can try: 156 | ```javascript 157 | > bob.transfer("Alice", 10, ["Carol", "Dave"]); 158 | Initiating transfer. 159 | Promise { } 160 | Transfer submitted (txHash = "0x9b3f51f3511c6797789862ce745a81d5bdfb00304831a8f25cc8554ea7597860"). 161 | Transfer of 10 was successful. Balance now 10. 162 | ``` 163 | The meaning of this syntax is that Carol and Dave are being included, along with Bob and Alice, in Bob's transaction's _anonymity set_. As a consequence, _no outside observer_ will be able to distinguish, among the four participants, who sent funds, who received funds, and how much was transferred. The account balances of all four participants are also private. 164 | 165 | In fact, you can see for yourself the perspective of Eve—an eavesdropper, let's say. In a new window (if you want), execute: 166 | ```javascript 167 | > let inputs; 168 | > zsc.contract._jsonInterface.forEach((element) => { if (element['name'] == "transfer") inputs = element['inputs']; }); 169 | > let parsed; 170 | > web3.eth.getTransaction('0x9b3f51f3511c6797789862ce745a81d5bdfb00304831a8f25cc8554ea7597860').then((transaction) => { parsed = web3.eth.abi.decodeParameters(inputs, "0x" + transaction.input.slice(10)); }); 171 | ``` 172 | You will see a bunch of fields; in particular, `parsed['y']` will contain the list of public keys, while `parsed['C']`, `parsed['D']` and `parsed['proof']` will contain further bytes which reveal nothing about the transaction. 173 | 174 | Anonymous Zether also supports native transaction fees. The idea is that you can circumvent the "gas linkability" issue by submitting each new transaction from a fresh, randomly generated Ethereum address, and furthermore specifying a gas price of 0. By routing a "tip" to a miner's Zether account, you may induce the miner to process your transaction anyway (see "Paying gas in ZTH through economic abstraction", Appendix F of the [original Zether paper](https://eprint.iacr.org/2019/191.pdf)). To do this, change the value at [this line](./packages/protocol/contracts/ZetherVerifier.sol#L16) before deploying, for example to 1. Finally, include the miner's Zether account as a 4th parameter in your `transfer` call. For example: 175 | ```javascript 176 | > bob.transfer("Alice", 10, ["Carol", "Dave"], "Miner") 177 | ``` 178 | In the miner's console, you should see: 179 | ```javascript 180 | > Fee of 1 received! Balance now 1. 181 | -------------------------------------------------------------------------------- /docs/AnonZether.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/anonymous-zether/be12ba6336607b26f235730a0c792a00d79625b5/docs/AnonZether.pdf -------------------------------------------------------------------------------- /docs/Infrastructure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Consensys/anonymous-zether/be12ba6336607b26f235730a0c792a00d79625b5/docs/Infrastructure.pdf -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "3.14.0", 3 | "packages": ["packages/*"], 4 | "version": "independent", 5 | "command": { 6 | "publish": { 7 | "ignoreChanges": ["test/**/*", "*.md", "scripts", "lib"] 8 | } 9 | }, 10 | "npmClient": "npm", 11 | "useWorkspaces": true 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anonymous-zether-monorepo", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "", 6 | "workspaces": [ 7 | "packages/**" 8 | ], 9 | "devDependencies": { 10 | "eslint-import-resolver-lerna": "^1.1.0", 11 | "lerna": "3.14.0" 12 | }, 13 | "dependencies": { 14 | "bn.js": "^5.1.1", 15 | "elliptic": "^6.5.1", 16 | "web3": "^1.7.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/anonymous.js/.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["plugins/markdown"], 3 | "recurseDepth": 10, 4 | "source": { 5 | "includePattern": ".+\\.js(doc|x)?$", 6 | "excludePattern": "(^|\\/|\\\\)_", 7 | "include": ["./"], 8 | "exclude": ["lib", "node_modules"] 9 | }, 10 | "sourceType": "module", 11 | "templates": { 12 | "disableSort": false, 13 | "collapse": true 14 | }, 15 | "opts": { 16 | "private": true, 17 | "recurse": true, 18 | "encoding": "utf-8" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/anonymous.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anonymous-zether/anonymous.js", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "./lib", 6 | "dependencies": { 7 | "bn.js": "^5.1.1", 8 | "elliptic": "^6.5.1", 9 | "web3": "^1.7.4", 10 | "web3-eth-abi": "^1.7.4", 11 | "web3-utils": "^1.7.4" 12 | }, 13 | "devDependencies": { 14 | "eslint": "^5.16.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/anonymous.js/src/client.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const BN = require('bn.js'); 3 | 4 | const utils = require('./utils/utils.js'); 5 | const { ElGamal } = require('./utils/algebra.js'); 6 | const Service = require('./utils/service.js'); 7 | const bn128 = require('./utils/bn128.js'); 8 | 9 | const sleep = (wait) => new Promise((resolve) => { setTimeout(resolve, wait); }); 10 | 11 | class Client { 12 | constructor(web3, zsc, home) { 13 | if (web3 === undefined) 14 | throw "Constructor's first argument should be an initialized Web3 object."; 15 | if (zsc === undefined) 16 | throw "Constructor's second argument should be a deployed ZSC contract object."; 17 | if (home === undefined) 18 | throw "Constructor's third argument should be the address of an unlocked Ethereum account."; 19 | 20 | web3.transactionConfirmationBlocks = 1; 21 | const that = this; 22 | 23 | const transfers = new Set(); 24 | let epochLength = undefined; 25 | let fee = undefined; 26 | 27 | const getEpoch = (timestamp) => { 28 | return Math.floor((timestamp === undefined ? (new Date).getTime() / 1000 : timestamp) / epochLength); 29 | }; 30 | 31 | const away = () => { // returns ms away from next epoch change 32 | const current = (new Date).getTime(); 33 | return Math.ceil(current / (epochLength * 1000)) * (epochLength * 1000) - current; 34 | }; 35 | 36 | const estimate = (size, contract) => { 37 | // this expression is meant to be a relatively close upper bound of the time that proving + a few verifications will take, as a function of anonset size 38 | // this function should hopefully give you good epoch lengths also for 8, 16, 32, etc... if you have very heavy traffic, may need to bump it up (many verifications) 39 | // i calibrated this on _my machine_. if you are getting transfer failures, you might need to bump up the constants, recalibrate yourself, etc. 40 | return Math.ceil(size * Math.log(size) / Math.log(2) * 20 + 5200) + (contract ? 20 : 0); 41 | // the 20-millisecond buffer is designed to give the callback time to fire (see below). 42 | }; 43 | 44 | zsc.events.TransferOccurred({}) // i guess this will just filter for "from here on out." 45 | // an interesting prospect is whether balance recovery could be eliminated by looking at past events. 46 | .on('data', (event) => { 47 | if (transfers.has(event.transactionHash)) { 48 | transfers.delete(event.transactionHash); 49 | return; 50 | } 51 | const account = this.account; 52 | if (event.returnValues['parties'] === null) return; // truffle is sometimes emitting spurious empty events??? have to avoid this case manually. 53 | event.returnValues['parties'].forEach((party, i) => { 54 | if (account.keypair['y'].eq(bn128.deserialize(party))) { 55 | const blockNumber = event.blockNumber; 56 | web3.eth.getBlock(blockNumber).then((block) => { 57 | account._state = account._simulate(block.timestamp); 58 | web3.eth.getTransaction(event.transactionHash).then((transaction) => { 59 | let inputs; 60 | zsc._jsonInterface.forEach((element) => { 61 | if (element['name'] === "transfer") 62 | inputs = element['inputs']; 63 | }); 64 | const parameters = web3.eth.abi.decodeParameters(inputs, "0x" + transaction.input.slice(10)); 65 | const value = utils.readBalance(parameters['C'][i], parameters['D'], account.keypair['x']); 66 | if (value > 0) { 67 | account._state.pending += value; 68 | console.log("Transfer of " + value + " received! Balance now " + (account._state.available + account._state.pending) + "."); 69 | } 70 | }); 71 | }); 72 | } 73 | }); 74 | if (account.keypair['y'].eq(bn128.deserialize(event.returnValues['beneficiary']))) { 75 | account._state.pending += fee; 76 | console.log("Fee of " + fee + " received! Balance now " + (account._state.available + account._state.pending) + "."); 77 | } 78 | }) 79 | .on('error', (error) => { 80 | console.log(error); // when will this be called / fired...?! confusing. also, test this. 81 | }); 82 | 83 | this.account = new function() { 84 | this.keypair = undefined; 85 | this._state = { 86 | available: 0, 87 | pending: 0, 88 | nonceUsed: 0, 89 | lastRollOver: 0 90 | }; 91 | 92 | this._simulate = (timestamp) => { 93 | const updated = {}; 94 | updated.available = this._state.available; 95 | updated.pending = this._state.pending; 96 | updated.nonceUsed = this._state.nonceUsed; 97 | updated.lastRollOver = getEpoch(timestamp); 98 | if (this._state.lastRollOver < updated.lastRollOver) { 99 | updated.available += updated.pending; 100 | updated.pending = 0; 101 | updated.nonceUsed = false; 102 | } 103 | return updated; 104 | }; 105 | 106 | this.balance = () => this._state.available + this._state.pending; 107 | this.public = () => bn128.serialize(this.keypair['y']); 108 | this.secret = () => "0x" + this.keypair['x'].toString(16, 64); 109 | }; 110 | 111 | this.friends = new function() { 112 | const friends = {}; 113 | this.add = (name, pubkey) => { 114 | // todo: checks that these are properly formed, of the right types, etc... 115 | friends[name] = bn128.deserialize(pubkey); 116 | return "Friend added."; 117 | }; 118 | 119 | this.show = () => friends; 120 | this.remove = (name) => { 121 | if (!(name in friends)) 122 | throw "Friend " + name + " not found in directory!"; 123 | delete friends[name]; 124 | return "Friend deleted."; 125 | }; 126 | }; 127 | 128 | this.register = (secret) => { 129 | return Promise.all([zsc.methods.epochLength().call(), zsc.methods.fee().call()]).then((result) => { 130 | epochLength = parseInt(result[0]); 131 | fee = parseInt(result[1]); 132 | return new Promise((resolve, reject) => { 133 | if (secret === undefined) { 134 | const keypair = utils.createAccount(); 135 | const [c, s] = utils.sign(zsc._address, keypair); 136 | zsc.methods.register(bn128.serialize(keypair['y']), c, s).send({ 'from': home, 'gas': 6721975 }) 137 | .on('transactionHash', (hash) => { 138 | console.log("Registration submitted (txHash = \"" + hash + "\")."); 139 | }) 140 | .on('receipt', (receipt) => { 141 | that.account.keypair = keypair; 142 | console.log("Registration successful."); 143 | resolve(); 144 | }) 145 | .on('error', (error) => { 146 | console.log("Registration failed: " + error); 147 | reject(error); 148 | }); 149 | } else { 150 | const x = new BN(secret.slice(2), 16).toRed(bn128.q); 151 | that.account.keypair = { 'x': x, 'y': bn128.curve.g.mul(x) }; 152 | zsc.methods.simulateAccounts([bn128.serialize(this.account.keypair['y'])], getEpoch() + 1).call().then((result) => { 153 | const simulated = result[0]; 154 | that.account._state.available = utils.readBalance(simulated[0], simulated[1], x); 155 | console.log("Account recovered successfully."); 156 | resolve(); // warning: won't register you. assuming you registered when you first created the account. 157 | }); 158 | } 159 | }); 160 | }); 161 | }; 162 | 163 | this.deposit = (value) => { 164 | if (this.account.keypair === undefined) 165 | throw "Client's account is not yet registered!"; 166 | const account = this.account; 167 | console.log("Initiating deposit."); 168 | return new Promise((resolve, reject) => { 169 | zsc.methods.fund(bn128.serialize(account.keypair['y']), value).send({ 'from': home, 'gas': 6721975 }) 170 | .on('transactionHash', (hash) => { 171 | console.log("Deposit submitted (txHash = \"" + hash + "\")."); 172 | }) 173 | .on('receipt', (receipt) => { 174 | account._state = account._simulate(); // have to freshly call it 175 | account._state.pending += value; 176 | console.log("Deposit of " + value + " was successful. Balance now " + (account._state.available + account._state.pending) + "."); 177 | resolve(receipt); 178 | }) 179 | .on('error', (error) => { 180 | console.log("Deposit failed: " + error); 181 | reject(error); 182 | }); 183 | }); 184 | }; 185 | 186 | this.transfer = (name, value, decoys, beneficiary) => { // todo: make sure the beneficiary is registered. 187 | if (this.account.keypair === undefined) 188 | throw "Client's account is not yet registered!"; 189 | decoys = decoys ? decoys : []; 190 | const account = this.account; 191 | const state = account._simulate(); 192 | if (value + fee > state.available + state.pending) 193 | throw "Requested transfer amount of " + value + " (plus fee of " + fee + ") exceeds account balance of " + (state.available + state.pending) + "."; 194 | const wait = away(); 195 | const seconds = Math.ceil(wait / 1000); 196 | const plural = seconds === 1 ? "" : "s"; 197 | if (value > state.available) { 198 | console.log("Your transfer has been queued. Please wait " + seconds + " second" + plural + ", for the release of your funds..."); 199 | return sleep(wait).then(() => this.transfer(name, value, decoys, beneficiary)); 200 | } 201 | if (state.nonceUsed) { 202 | console.log("Your transfer has been queued. Please wait " + seconds + " second" + plural + ", until the next epoch..."); 203 | return sleep(wait).then(() => this.transfer(name, value, decoys, beneficiary)); 204 | } 205 | const size = 2 + decoys.length; 206 | const estimated = estimate(size, false); // see notes above 207 | if (estimated > epochLength * 1000) 208 | throw "The anonset size (" + size + ") you've requested might take longer than the epoch length (" + epochLength + " seconds) to prove. Consider re-deploying, with an epoch length at least " + Math.ceil(estimate(size, true) / 1000) + " seconds."; 209 | if (estimated > wait) { 210 | console.log(wait < 3100 ? "Initiating transfer." : "Your transfer has been queued. Please wait " + seconds + " second" + plural + ", until the next epoch..."); 211 | return sleep(wait).then(() => this.transfer(name, value, decoys, beneficiary)); 212 | } 213 | if (size & (size - 1)) { 214 | let previous = 1; 215 | let next = 2; 216 | while (next < size) { 217 | previous *= 2; 218 | next *= 2; 219 | } 220 | throw "Anonset's size (including you and the recipient) must be a power of two. Add " + (next - size) + " or remove " + (size - previous) + "."; 221 | } 222 | const friends = this.friends.show(); 223 | if (!(name in friends)) 224 | throw "Name \"" + name + "\" hasn't been friended yet!"; 225 | if (account.keypair['y'].eq(friends[name])) 226 | throw "Sending to yourself is currently unsupported (and useless!)." 227 | const y = [account.keypair['y'], friends[name]]; // not yet shuffled 228 | decoys.forEach((decoy) => { 229 | if (!(decoy in friends)) 230 | throw "Decoy \"" + decoy + "\" is unknown in friends directory!"; 231 | y.push(friends[decoy]); 232 | }); 233 | if (beneficiary !== undefined && !(beneficiary in friends)) 234 | throw "Beneficiary \"" + beneficiary + "\" is not known!"; 235 | const index = []; 236 | let m = y.length; 237 | while (m !== 0) { // https://bost.ocks.org/mike/shuffle/ 238 | const i = crypto.randomBytes(1).readUInt8() % m--; // warning: N should be <= 256. also modulo bias. 239 | const temp = y[i]; 240 | y[i] = y[m]; 241 | y[m] = temp; 242 | if (account.keypair['y'].eq(temp)) index[0] = m; 243 | else if (friends[name].eq(temp)) index[1] = m; 244 | } // shuffle the array of y's 245 | if (index[0] % 2 === index[1] % 2) { 246 | const temp = y[index[1]]; 247 | y[index[1]] = y[index[1] + (index[1] % 2 === 0 ? 1 : -1)]; 248 | y[index[1] + (index[1] % 2 === 0 ? 1 : -1)] = temp; 249 | index[1] = index[1] + (index[1] % 2 === 0 ? 1 : -1); 250 | } // make sure you and your friend have opposite parity 251 | return new Promise((resolve, reject) => { 252 | zsc.methods.simulateAccounts(y.map(bn128.serialize), getEpoch()).call().then((result) => { 253 | const deserialized = result.map((account) => ElGamal.deserialize(account)); 254 | if (deserialized.some((account) => account.zero())) 255 | return reject(new Error("Please make sure all parties (including decoys) are registered.")); // todo: better error message, i.e., which friend? 256 | const r = bn128.randomScalar(); 257 | const D = bn128.curve.g.mul(r); 258 | const C = y.map((party, i) => { 259 | const left = ElGamal.base['g'].mul(new BN(i === index[0] ? -value - fee : i === index[1] ? value : 0)).add(party.mul(r)) 260 | return new ElGamal(left, D) 261 | }); 262 | const Cn = deserialized.map((account, i) => account.add(C[i])); 263 | const proof = Service.proveTransfer(Cn, C, y, state.lastRollOver, account.keypair['x'], r, value, state.available - value - fee, index, fee); 264 | const u = utils.u(state.lastRollOver, account.keypair['x']); 265 | const throwaway = web3.eth.accounts.create(); 266 | const beneficiaryKey = beneficiary === undefined ? bn128.zero : friends[beneficiary]; 267 | const encoded = zsc.methods.transfer(C.map((ciphertext) => bn128.serialize(ciphertext.left())), bn128.serialize(D), y.map(bn128.serialize), bn128.serialize(u), proof.serialize(), bn128.serialize(beneficiaryKey)).encodeABI(); 268 | const tx = { 'to': zsc._address, 'data': encoded, 'gas': 7721975, 'nonce': 0 }; 269 | web3.eth.accounts.signTransaction(tx, throwaway.privateKey).then((signed) => { 270 | web3.eth.sendSignedTransaction(signed.rawTransaction) 271 | .on('transactionHash', (hash) => { 272 | transfers.add(hash); 273 | console.log("Transfer submitted (txHash = \"" + hash + "\")."); 274 | }) 275 | .on('receipt', (receipt) => { 276 | account._state = account._simulate(); // have to freshly call it 277 | account._state.nonceUsed = true; 278 | account._state.pending -= value + fee; 279 | console.log("Transfer of " + value + " (with fee of " + fee + ") was successful. Balance now " + (account._state.available + account._state.pending) + "."); 280 | resolve(receipt); 281 | }) 282 | .on('error', (error) => { 283 | console.log("Transfer failed: " + error); 284 | reject(error); 285 | }); 286 | }); 287 | }); 288 | }); 289 | }; 290 | 291 | this.withdraw = (value) => { 292 | if (this.account.keypair === undefined) 293 | throw "Client's account is not yet registered!"; 294 | const account = this.account; 295 | const state = account._simulate(); 296 | if (value > state.available + state.pending) 297 | throw "Requested withdrawal amount of " + value + " exceeds account balance of " + (state.available + state.pending) + "."; 298 | const wait = away(); 299 | const seconds = Math.ceil(wait / 1000); 300 | const plural = seconds === 1 ? "" : "s"; 301 | if (value > state.available) { 302 | console.log("Your withdrawal has been queued. Please wait " + seconds + " second" + plural + ", for the release of your funds..."); 303 | return sleep(wait).then(() => this.withdraw(value)); 304 | } 305 | if (state.nonceUsed) { 306 | console.log("Your withdrawal has been queued. Please wait " + seconds + " second" + plural + ", until the next epoch..."); 307 | return sleep(wait).then(() => this.withdraw(value)); 308 | } 309 | if (3100 > wait) { // determined empirically. IBFT, block time 1 310 | console.log("Initiating withdrawal."); 311 | return sleep(wait).then(() => this.withdraw(value)); 312 | } 313 | return new Promise((resolve, reject) => { 314 | zsc.methods.simulateAccounts([bn128.serialize(account.keypair['y'])], getEpoch()).call() 315 | .then((result) => { 316 | const deserialized = ElGamal.deserialize(result[0]); 317 | const C = deserialized.plus(new BN(-value)); 318 | const proof = Service.proveBurn(C, account.keypair['y'], state.lastRollOver, home, account.keypair['x'], state.available - value); 319 | const u = utils.u(state.lastRollOver, account.keypair['x']); 320 | zsc.methods.burn(bn128.serialize(account.keypair['y']), value, bn128.serialize(u), proof.serialize()).send({ 'from': home, 'gas': 6721975 }) 321 | .on('transactionHash', (hash) => { 322 | console.log("Withdrawal submitted (txHash = \"" + hash + "\")."); 323 | }) 324 | .on('receipt', (receipt) => { 325 | account._state = account._simulate(); // have to freshly call it 326 | account._state.nonceUsed = true; 327 | account._state.pending -= value; 328 | console.log("Withdrawal of " + value + " was successful. Balance now " + (account._state.available + account._state.pending) + "."); 329 | resolve(receipt); 330 | }).on('error', (error) => { 331 | console.log("Withdrawal failed: " + error); 332 | reject(error); 333 | }); 334 | }); 335 | }); 336 | }; 337 | } 338 | } 339 | 340 | module.exports = Client; -------------------------------------------------------------------------------- /packages/anonymous.js/src/prover/burn.js: -------------------------------------------------------------------------------- 1 | const ABICoder = require('web3-eth-abi'); 2 | const BN = require('bn.js'); 3 | 4 | const bn128 = require('../utils/bn128.js'); 5 | const utils = require('../utils/utils.js'); 6 | const { PedersenCommitment, ElGamal, PedersenVectorCommitment, FieldVector } = require('../utils/algebra.js'); 7 | const { FieldVectorPolynomial } = require('../utils/misc.js'); 8 | const InnerProductProof = require('./innerproduct.js'); 9 | 10 | class BurnProof { 11 | constructor() { 12 | this.serialize = () => { // please initialize this before calling this method... 13 | let result = "0x"; 14 | result += bn128.representation(this.BA.point()).slice(2); 15 | result += bn128.representation(this.BS.point()).slice(2); 16 | 17 | result += bn128.representation(this.T_1.point()).slice(2); 18 | result += bn128.representation(this.T_2.point()).slice(2); 19 | result += bn128.bytes(this.tHat).slice(2); 20 | result += bn128.bytes(this.mu).slice(2); 21 | 22 | result += bn128.bytes(this.c).slice(2); 23 | result += bn128.bytes(this.s_sk).slice(2); 24 | result += bn128.bytes(this.s_b).slice(2); 25 | result += bn128.bytes(this.s_tau).slice(2); 26 | 27 | result += this.ipProof.serialize().slice(2); 28 | 29 | return result; 30 | } 31 | } 32 | static prove(statement, witness) { 33 | const result = new BurnProof(); 34 | 35 | const statementHash = utils.hash(ABICoder.encodeParameters([ 36 | 'bytes32[2]', 37 | 'bytes32[2]', 38 | 'bytes32[2]', 39 | 'uint256', 40 | 'address', 41 | ], [ 42 | bn128.serialize(statement['Cn'].left()), 43 | bn128.serialize(statement['Cn'].right()), 44 | bn128.serialize(statement['y']), 45 | statement['epoch'], 46 | statement['sender'], 47 | ])); // useless to break this out up top. "psychologically" easier 48 | 49 | witness['bDiff'] = new BN(witness['bDiff']).toRed(bn128.q); 50 | 51 | const aL = new FieldVector(witness['bDiff'].toString(2, 32).split("").reverse().map((i) => new BN(i, 2).toRed(bn128.q))); 52 | const aR = aL.plus(new BN(1).toRed(bn128.q).redNeg()); 53 | result.BA = PedersenVectorCommitment.commit(aL, aR); 54 | const sL = new FieldVector(Array.from({ length: 32 }).map(bn128.randomScalar)); 55 | const sR = new FieldVector(Array.from({ length: 32 }).map(bn128.randomScalar)); 56 | result.BS = PedersenVectorCommitment.commit(sL, sR); 57 | 58 | const y = utils.hash(ABICoder.encodeParameters([ 59 | 'bytes32', 60 | 'bytes32[2]', 61 | 'bytes32[2]', 62 | ], [ 63 | bn128.bytes(statementHash), 64 | bn128.serialize(result.BA.point()), 65 | bn128.serialize(result.BS.point()), 66 | ])); 67 | 68 | const ys = new FieldVector([new BN(1).toRed(bn128.q)]); 69 | for (let i = 1; i < 32; i++) { // it would be nice to have a nifty functional way of doing this. 70 | ys.push(ys.getVector()[i - 1].redMul(y)); 71 | } 72 | const z = utils.hash(bn128.bytes(y)); 73 | const zs = [z.redPow(new BN(2))]; 74 | const twos = [] 75 | for (let i = 0; i < 32; i++) twos[i] = new BN(1).shln(i).toRed(bn128.q); 76 | const twoTimesZs = new FieldVector(twos).times(zs[0]); 77 | 78 | const lPoly = new FieldVectorPolynomial(aL.plus(z.redNeg()), sL); 79 | const rPoly = new FieldVectorPolynomial(ys.hadamard(aR.plus(z)).add(twoTimesZs), sR.hadamard(ys)); 80 | const tPolyCoefficients = lPoly.innerProduct(rPoly); // just an array of BN Reds... should be length 3 81 | result.T_1 = PedersenCommitment.commit(tPolyCoefficients[1]); 82 | result.T_2 = PedersenCommitment.commit(tPolyCoefficients[2]); 83 | 84 | const x = utils.hash(ABICoder.encodeParameters([ 85 | 'bytes32', 86 | 'bytes32[2]', 87 | 'bytes32[2]', 88 | ], [ 89 | bn128.bytes(z), 90 | bn128.serialize(result.T_1.point()), 91 | bn128.serialize(result.T_2.point()), 92 | ])); 93 | 94 | result.tHat = tPolyCoefficients[0].redAdd(tPolyCoefficients[1].redMul(x)).redAdd(tPolyCoefficients[2].redMul(x.redPow(new BN(2)))); 95 | const tauX = result.T_1.randomness.redMul(x).redAdd(result.T_2.randomness.redMul(x.redPow(new BN(2)))); 96 | result.mu = result.BA.randomness.redAdd(result.BS.randomness.redMul(x)); 97 | 98 | const k_sk = bn128.randomScalar(); 99 | const k_b = bn128.randomScalar(); 100 | const k_tau = bn128.randomScalar(); 101 | 102 | const A_y = bn128.curve.g.mul(k_sk); 103 | const A_b = ElGamal.base['g'].mul(k_b).add(statement['Cn'].right().mul(zs[0]).mul(k_sk)); // wasted exponentiation 104 | const A_t = ElGamal.base['g'].mul(k_b.redNeg()).add(PedersenCommitment.base['h'].mul(k_tau)); 105 | const A_u = utils.gEpoch(statement['epoch']).mul(k_sk); 106 | 107 | result.c = utils.hash(ABICoder.encodeParameters([ 108 | 'bytes32', 109 | 'bytes32[2]', 110 | 'bytes32[2]', 111 | 'bytes32[2]', 112 | 'bytes32[2]', 113 | ], [ 114 | bn128.bytes(x), 115 | bn128.serialize(A_y), 116 | bn128.serialize(A_b), 117 | bn128.serialize(A_t), 118 | bn128.serialize(A_u), 119 | ])); 120 | 121 | result.s_sk = k_sk.redAdd(result.c.redMul(witness['sk'])); 122 | result.s_b = k_b.redAdd(result.c.redMul(witness['bDiff'].redMul(zs[0]))); 123 | result.s_tau = k_tau.redAdd(result.c.redMul(tauX)); 124 | 125 | const hOld = PedersenVectorCommitment.base['h']; 126 | const gsOld = PedersenVectorCommitment.base['gs']; // horrible hack, but works. 127 | const hsOld = PedersenVectorCommitment.base['hs']; 128 | 129 | const o = utils.hash(ABICoder.encodeParameters([ 130 | 'bytes32', 131 | ], [ 132 | bn128.bytes(result.c), 133 | ])); 134 | PedersenVectorCommitment.base['h'] = PedersenVectorCommitment.base['h'].mul(o); 135 | PedersenVectorCommitment.base['gs'] = PedersenVectorCommitment.base['gs'].slice(0, 32); 136 | PedersenVectorCommitment.base['hs'] = PedersenVectorCommitment.base['hs'].slice(0, 32).hadamard(ys.invert()); 137 | 138 | const P = new PedersenVectorCommitment(bn128.zero); // P.decommit(lPoly.evaluate(x), rPoly.evaluate(x), result.tHat); 139 | P.gValues = lPoly.evaluate(x); 140 | P.hValues = rPoly.evaluate(x); 141 | P.randomness = result.tHat; 142 | result.ipProof = InnerProductProof.prove(P, o); 143 | 144 | PedersenVectorCommitment.base['h'] = hOld; 145 | PedersenVectorCommitment.base['gs'] = gsOld; 146 | PedersenVectorCommitment.base['hs'] = hsOld; 147 | return result; 148 | } 149 | } 150 | 151 | module.exports = BurnProof; -------------------------------------------------------------------------------- /packages/anonymous.js/src/prover/innerproduct.js: -------------------------------------------------------------------------------- 1 | const ABICoder = require('web3-eth-abi'); 2 | const { PedersenVectorCommitment } = require('../utils/algebra.js'); 3 | const bn128 = require('../utils/bn128.js'); 4 | const utils = require('../utils/utils.js'); 5 | 6 | class InnerProductProof { 7 | constructor() { 8 | this.serialize = () => { 9 | let result = "0x"; 10 | this.L.forEach((l) => { result += bn128.representation(l).slice(2); }); 11 | this.R.forEach((r) => { result += bn128.representation(r).slice(2); }); 12 | result += bn128.bytes(this.a).slice(2); 13 | result += bn128.bytes(this.b).slice(2); 14 | return result; 15 | }; 16 | } 17 | 18 | static prove(commitment, salt) { // arg: a vector commitment which was decommited. 19 | const result = new InnerProductProof(); 20 | result.L = []; 21 | result.R = []; 22 | 23 | const recursiveProof = (result, as, bs, previousChallenge) => { // ref to result 24 | const n = as.length(); 25 | if (as.length() === 1) { 26 | result.a = as.getVector()[0]; 27 | result.b = bs.getVector()[0]; 28 | return; 29 | } 30 | const nPrime = n / 2; // what if this is not an integer?!? 31 | const asLeft = as.slice(0, nPrime); 32 | const asRight = as.slice(nPrime); 33 | const bsLeft = bs.slice(0, nPrime); 34 | const bsRight = bs.slice(nPrime); 35 | const gsLeft = PedersenVectorCommitment.base['gs'].slice(0, nPrime); 36 | const gsRight = PedersenVectorCommitment.base['gs'].slice(nPrime); 37 | const hsLeft = PedersenVectorCommitment.base['hs'].slice(0, nPrime); 38 | const hsRight = PedersenVectorCommitment.base['hs'].slice(nPrime); 39 | 40 | const cL = asLeft.innerProduct(bsRight); 41 | const cR = asRight.innerProduct(bsLeft); 42 | const L = gsRight.multiExponentiate(asLeft).add(hsLeft.multiExponentiate(bsRight)).add(PedersenVectorCommitment.base['h'].mul(cL)); 43 | const R = gsLeft.multiExponentiate(asRight).add(hsRight.multiExponentiate(bsLeft)).add(PedersenVectorCommitment.base['h'].mul(cR)); 44 | result.L.push(L); 45 | result.R.push(R); 46 | 47 | const x = utils.hash(ABICoder.encodeParameters([ 48 | 'bytes32', 49 | 'bytes32[2]', 50 | 'bytes32[2]', 51 | ], [ 52 | bn128.bytes(previousChallenge), 53 | bn128.serialize(L), 54 | bn128.serialize(R), 55 | ])); 56 | 57 | const xInv = x.redInvm(); 58 | PedersenVectorCommitment.base['gs'] = gsLeft.times(xInv).add(gsRight.times(x)); 59 | PedersenVectorCommitment.base['hs'] = hsLeft.times(x).add(hsRight.times(xInv)); 60 | const asPrime = asLeft.times(x).add(asRight.times(xInv)); 61 | const bsPrime = bsLeft.times(xInv).add(bsRight.times(x)); 62 | 63 | recursiveProof(result, asPrime, bsPrime, x); 64 | 65 | PedersenVectorCommitment.base['gs'] = gsLeft.concat(gsRight); 66 | PedersenVectorCommitment.base['hs'] = hsLeft.concat(hsRight); // clean up 67 | }; 68 | recursiveProof(result, commitment.gValues, commitment.hValues, salt); 69 | return result; 70 | } 71 | } 72 | 73 | module.exports = InnerProductProof; -------------------------------------------------------------------------------- /packages/anonymous.js/src/prover/zether.js: -------------------------------------------------------------------------------- 1 | const ABICoder = require('web3-eth-abi'); 2 | const BN = require('bn.js'); 3 | 4 | const bn128 = require('../utils/bn128.js'); 5 | const utils = require('../utils/utils.js'); 6 | const { PedersenCommitment, ElGamal, PedersenVectorCommitment, FieldVector, PointVector, ElGamalVector } = require('../utils/algebra.js'); 7 | const { Convolver, FieldVectorPolynomial, Polynomial } = require('../utils/misc.js'); 8 | const InnerProductProof = require('./innerproduct.js'); 9 | 10 | class ZetherProof { 11 | constructor() { 12 | this.serialize = () => { // please initialize this before calling this method... 13 | let result = "0x"; 14 | result += bn128.representation(this.BA.point()).slice(2); 15 | result += bn128.representation(this.BS.point()).slice(2); 16 | result += bn128.representation(this.A.point()).slice(2); 17 | result += bn128.representation(this.B.point()).slice(2); 18 | 19 | this.CnG.forEach((CnG_k) => { result += bn128.representation(CnG_k.left()).slice(2); }); 20 | this.CnG.forEach((CnG_k) => { result += bn128.representation(CnG_k.right()).slice(2); }); 21 | this.C_0G.forEach((C_0G_k) => { result += bn128.representation(C_0G_k.left()).slice(2); }); 22 | this.C_0G.forEach((C_0G_k) => { result += bn128.representation(C_0G_k.right()).slice(2); }); 23 | this.y_0G.forEach((y_0G_k) => { result += bn128.representation(y_0G_k.left()).slice(2); }); 24 | this.y_0G.forEach((y_0G_k) => { result += bn128.representation(y_0G_k.right()).slice(2); }); 25 | this.C_XG.forEach((C_XG_k) => { result += bn128.representation(C_XG_k.left()).slice(2); }); 26 | this.C_XG.forEach((C_XG_k) => { result += bn128.representation(C_XG_k.right()).slice(2); }); 27 | this.f.getVector().forEach((f_k) => { result += bn128.bytes(f_k).slice(2); }); 28 | 29 | result += bn128.bytes(this.z_A).slice(2); 30 | 31 | result += bn128.representation(this.T_1.point()).slice(2); 32 | result += bn128.representation(this.T_2.point()).slice(2); 33 | result += bn128.bytes(this.tHat).slice(2); 34 | result += bn128.bytes(this.mu).slice(2); 35 | 36 | result += bn128.bytes(this.c).slice(2); 37 | result += bn128.bytes(this.s_sk).slice(2); 38 | result += bn128.bytes(this.s_r).slice(2); 39 | result += bn128.bytes(this.s_b).slice(2); 40 | result += bn128.bytes(this.s_tau).slice(2); 41 | 42 | result += this.ipProof.serialize().slice(2); 43 | 44 | return result; 45 | }; 46 | } 47 | 48 | static prove(statement, witness, fee) { 49 | const result = new ZetherProof(); 50 | 51 | const statementHash = utils.hash(ABICoder.encodeParameters([ 52 | 'bytes32[2][]', 53 | 'bytes32[2][]', 54 | 'bytes32[2][]', 55 | 'bytes32[2]', 56 | 'bytes32[2][]', 57 | 'uint256', 58 | ], [ 59 | statement['Cn'].map((Cn_i) => bn128.serialize(Cn_i.left())), 60 | statement['Cn'].map((Cn_i) => bn128.serialize(Cn_i.right())), 61 | statement['C'].map((C_i) => bn128.serialize(C_i.left())), 62 | bn128.serialize(statement['C'][0].right()), 63 | statement['y'].map((key) => bn128.serialize(key)), 64 | statement['epoch'], 65 | ])); 66 | 67 | statement['Cn'] = new ElGamalVector(statement['Cn']); 68 | // statement['C'] = new ElGamalVector(statement['C']); 69 | statement['y'] = new PointVector(statement['y']); 70 | witness['bTransfer'] = new BN(witness['bTransfer']).toRed(bn128.q); 71 | witness['bDiff'] = new BN(witness['bDiff']).toRed(bn128.q); 72 | 73 | const index = witness['index']; 74 | const key = statement['y'].getVector()[index[0]]; 75 | const number = witness['bTransfer'].add(witness['bDiff'].shln(32)); // shln a red? check 76 | const decomposition = number.toString(2, 64).split("").reverse(); 77 | const aL = new FieldVector(Array.from({ 'length': 64 }).map((_, i) => new BN(decomposition[i], 2).toRed(bn128.q))); 78 | const aR = aL.plus(new BN(1).toRed(bn128.q).redNeg()) 79 | result.BA = PedersenVectorCommitment.commit(aL, aR); 80 | const sL = new FieldVector(Array.from({ length: 64 }).map(bn128.randomScalar)); 81 | const sR = new FieldVector(Array.from({ length: 64 }).map(bn128.randomScalar)); 82 | result.BS = PedersenVectorCommitment.commit(sL, sR); 83 | 84 | const N = statement['y'].length(); 85 | if (N & (N - 1)) throw "Size must be a power of 2!"; // probably unnecessary... this won't be called directly. 86 | const m = new BN(N).bitLength() - 1; // assuming that N is a power of 2? 87 | const a = new FieldVector(Array.from({ 'length': 2 * m }).map(bn128.randomScalar)); 88 | const b = new FieldVector((new BN(witness['index'][1]).toString(2, m) + new BN(index[0]).toString(2, m)).split("").reverse().map((i) => new BN(i, 2).toRed(bn128.q))); 89 | const c = a.hadamard(b.times(new BN(2).toRed(bn128.q)).negate().plus(new BN(1).toRed(bn128.q))); 90 | const d = a.hadamard(a).negate(); 91 | const e = new FieldVector([a.getVector()[0].redMul(a.getVector()[m]), a.getVector()[0].redMul(a.getVector()[m])]); 92 | const f = new FieldVector([a.getVector()[b.getVector()[0].toNumber() * m], a.getVector()[b.getVector()[m].toNumber() * m].redNeg()]); 93 | result.A = PedersenVectorCommitment.commit(a, d.concat(e)); // warning: semantic change for contract 94 | result.B = PedersenVectorCommitment.commit(b, c.concat(f)); // warning: semantic change for contract 95 | 96 | const v = utils.hash(ABICoder.encodeParameters([ 97 | 'bytes32', 98 | 'bytes32[2]', 99 | 'bytes32[2]', 100 | 'bytes32[2]', 101 | 'bytes32[2]', 102 | ], [ 103 | bn128.bytes(statementHash), 104 | bn128.serialize(result.BA.point()), 105 | bn128.serialize(result.BS.point()), 106 | bn128.serialize(result.A.point()), 107 | bn128.serialize(result.B.point()), 108 | ])); 109 | 110 | const recursivePolynomials = (list, a, b) => { 111 | if (a.length === 0) return list; 112 | const aTop = a.pop(); 113 | const bTop = b.pop(); 114 | const left = new Polynomial([aTop.redNeg(), new BN(1).toRed(bn128.q).redSub(bTop)]); // X - f_k(X) 115 | const right = new Polynomial([aTop, bTop]); // f_k(X) 116 | for (let i = 0; i < list.length; i++) list[i] = [list[i].mul(left), list[i].mul(right)]; 117 | return recursivePolynomials(list.flat(), a, b); 118 | } 119 | let P_poly = recursivePolynomials([new Polynomial([new BN(1).toRed(bn128.q)])], a.getVector().slice(0, m), b.getVector().slice(0, m)); 120 | let Q_poly = recursivePolynomials([new Polynomial([new BN(1).toRed(bn128.q)])], a.getVector().slice(m), b.getVector().slice(m)); 121 | P_poly = Array.from({ length: m }).map((_, k) => new FieldVector(P_poly.map((P_i) => P_i.coefficients[k]))); 122 | Q_poly = Array.from({ length: m }).map((_, k) => new FieldVector(Q_poly.map((Q_i) => Q_i.coefficients[k]))); 123 | 124 | const Phi = Array.from({ length: m }).map(() => ElGamal.commit(key, new BN(0).toRed(bn128.q))); 125 | const Chi = Array.from({ length: m }).map(() => ElGamal.commit(key, new BN(0).toRed(bn128.q))); 126 | const Psi = Array.from({ length: m }).map(() => ElGamal.commit(key, new BN(0).toRed(bn128.q))); 127 | 128 | result.CnG = Array.from({ length: m }).map((_, k) => statement['Cn'].multiExponentiate(P_poly[k]).add(Phi[k])); 129 | result.C_0G = Array.from({ length: m }).map((_, k) => { 130 | const left = new PointVector(statement['C'].map((C_i) => C_i.left())).multiExponentiate(P_poly[k]).add(Chi[k].left()); 131 | return new ElGamal(left, Chi[k].right()); 132 | }); 133 | result.y_0G = Array.from({ length: m }).map((_, k) => { 134 | const left = statement['y'].multiExponentiate(P_poly[k]).add(Psi[k].left()); 135 | return new ElGamal(left, Psi[k].right()); 136 | }); 137 | result.C_XG = Array.from({ length: m }).map(() => ElGamal.commit(statement['C'][0].right(), new BN(0).toRed(bn128.q))); 138 | 139 | let vPow = new BN(1).toRed(bn128.q); 140 | for (let i = 0; i < N; i++) { // could turn this into a complicated reduce, but... 141 | const poly = i % 2 ? Q_poly : P_poly; // clunky, i know, etc. etc. 142 | result.C_XG = result.C_XG.map((C_XG_k, k) => C_XG_k.plus(vPow.redMul(witness['bTransfer'].redNeg().redSub(new BN(fee).toRed(bn128.q)).redMul(poly[k].getVector()[(witness['index'][0] + N - (i - i % 2)) % N]).redAdd(witness['bTransfer'].redMul(poly[k].getVector()[(witness['index'][1] + N - (i - i % 2)) % N]))))); 143 | if (i !== 0) 144 | vPow = vPow.redMul(v); 145 | } 146 | 147 | const w = utils.hash(ABICoder.encodeParameters([ 148 | 'bytes32', 149 | 'bytes32[2][]', 150 | 'bytes32[2][]', 151 | 'bytes32[2][]', 152 | 'bytes32[2][]', 153 | 'bytes32[2][]', 154 | 'bytes32[2][]', 155 | 'bytes32[2][]', 156 | 'bytes32[2][]', 157 | ], [ 158 | bn128.bytes(v), 159 | result.CnG.map((CnG_k) => bn128.serialize(CnG_k.left())), 160 | result.CnG.map((CnG_k) => bn128.serialize(CnG_k.right())), 161 | result.C_0G.map((C_0G_k) => bn128.serialize(C_0G_k.left())), 162 | result.C_0G.map((C_0G_k) => bn128.serialize(C_0G_k.right())), 163 | result.y_0G.map((y_0G_k) => bn128.serialize(y_0G_k.left())), 164 | result.y_0G.map((y_0G_k) => bn128.serialize(y_0G_k.right())), 165 | result.C_XG.map((C_XG_k) => bn128.serialize(C_XG_k.left())), 166 | result.C_XG.map((C_XG_k) => bn128.serialize(C_XG_k.right())), 167 | ])); 168 | 169 | result.f = b.times(w).add(a); 170 | result.z_A = result.B.randomness.redMul(w).redAdd(result.A.randomness); 171 | 172 | const y = utils.hash(ABICoder.encodeParameters([ 173 | 'bytes32', 174 | ], [ 175 | bn128.bytes(w), // that's it? 176 | ])); 177 | 178 | const ys = new FieldVector([new BN(1).toRed(bn128.q)]); 179 | for (let i = 1; i < 64; i++) { // it would be nice to have a nifty functional way of doing this. 180 | ys.push(ys.getVector()[i - 1].redMul(y)); 181 | } 182 | const z = utils.hash(bn128.bytes(y)); 183 | const zs = [z.redPow(new BN(2)), z.redPow(new BN(3))]; 184 | const twos = [] 185 | for (let i = 0; i < 32; i++) twos[i] = new BN(1).shln(i).toRed(bn128.q); 186 | const twoTimesZs = new FieldVector([]); 187 | for (let i = 0; i < 2; i++) { 188 | for (let j = 0; j < 32; j++) { 189 | twoTimesZs.push(zs[i].redMul(twos[j])); 190 | } 191 | } 192 | 193 | const lPoly = new FieldVectorPolynomial(aL.plus(z.redNeg()), sL); 194 | const rPoly = new FieldVectorPolynomial(ys.hadamard(aR.plus(z)).add(twoTimesZs), sR.hadamard(ys)); 195 | const tPolyCoefficients = lPoly.innerProduct(rPoly); // just an array of BN Reds... should be length 3 196 | result.T_1 = PedersenCommitment.commit(tPolyCoefficients[1]); 197 | result.T_2 = PedersenCommitment.commit(tPolyCoefficients[2]); 198 | 199 | const x = utils.hash(ABICoder.encodeParameters([ 200 | 'bytes32', 201 | 'bytes32[2]', 202 | 'bytes32[2]', 203 | ], [ 204 | bn128.bytes(z), 205 | bn128.serialize(result.T_1.point()), 206 | bn128.serialize(result.T_2.point()), 207 | ])); 208 | 209 | result.tHat = tPolyCoefficients[0].redAdd(tPolyCoefficients[1].redMul(x)).redAdd(tPolyCoefficients[2].redMul(x.redPow(new BN(2)))); 210 | const tauX = result.T_1.randomness.redMul(x).redAdd(result.T_2.randomness.redMul(x.redPow(new BN(2)))); 211 | result.mu = result.BA.randomness.redAdd(result.BS.randomness.redMul(x)); 212 | 213 | let CnR = new ElGamal(undefined, bn128.zero); // only need the RHS. this will give us CRnR 214 | let chi = new BN(0).toRed(bn128.q); // for DR 215 | let psi = new BN(0).toRed(bn128.q); // for gR 216 | let C_XR = new ElGamal(undefined, bn128.zero); // only need the RHS 217 | let p = new FieldVector(Array.from({ length: N }).map(() => new BN(0).toRed(bn128.q))); // evaluations of poly_0 and poly_1 at w. 218 | let q = new FieldVector(Array.from({ length: N }).map(() => new BN(0).toRed(bn128.q))); // verifier will compute these using f. 219 | 220 | let wPow = new BN(1).toRed(bn128.q); 221 | for (let k = 0; k < m; k++) { 222 | CnR = CnR.add(Phi[k].neg().mul(wPow)); 223 | chi = chi.redAdd(Chi[k].randomness.redMul(wPow)); 224 | psi = psi.redAdd(Psi[k].randomness.redMul(wPow)); 225 | C_XR = C_XR.add(result.C_XG[k].neg().mul(wPow)); 226 | p = p.add(P_poly[k].times(wPow)); 227 | q = q.add(Q_poly[k].times(wPow)); 228 | wPow = wPow.redMul(w); 229 | } 230 | CnR = CnR.add(statement['Cn'].getVector()[index[0]].mul(wPow)); 231 | const DR = statement['C'][0].right().mul(wPow).add(bn128.curve.g.mul(chi.redNeg())); 232 | const gR = bn128.curve.g.mul(wPow.redSub(psi)); 233 | p = p.add(new FieldVector(Array.from({ length: N }).map((_, i) => i === index[0] ? wPow : new BN().toRed(bn128.q)))); 234 | q = q.add(new FieldVector(Array.from({ length: N }).map((_, i) => i === index[1] ? wPow : new BN().toRed(bn128.q)))); 235 | 236 | const convolver = new Convolver(); 237 | convolver.prepare(statement['y']); 238 | const y_p = convolver.convolution(p); 239 | const y_q = convolver.convolution(q); 240 | vPow = new BN(1).toRed(bn128.q); 241 | for (let i = 0; i < N; i++) { 242 | const y_poly = i % 2 ? y_q : y_p; // this is weird. stumped. 243 | C_XR = C_XR.add(new ElGamal(undefined, y_poly.getVector()[Math.floor(i / 2)].mul(vPow))); 244 | if (i > 0) 245 | vPow = vPow.redMul(v); 246 | } 247 | 248 | const k_sk = bn128.randomScalar(); 249 | const k_r = bn128.randomScalar(); 250 | const k_b = bn128.randomScalar(); 251 | const k_tau = bn128.randomScalar(); 252 | 253 | const A_y = gR.mul(k_sk); 254 | const A_D = bn128.curve.g.mul(k_r); 255 | const A_b = ElGamal.base['g'].mul(k_b).add(DR.mul(zs[0].redNeg()).add(CnR.right().mul(zs[1])).mul(k_sk)); 256 | const A_X = C_XR.right().mul(k_r); // y_XR.mul(k_r); 257 | const A_t = ElGamal.base['g'].mul(k_b.redNeg()).add(PedersenCommitment.base['h'].mul(k_tau)); 258 | const A_u = utils.gEpoch(statement['epoch']).mul(k_sk); 259 | 260 | result.c = utils.hash(ABICoder.encodeParameters([ 261 | 'bytes32', 262 | 'bytes32[2]', 263 | 'bytes32[2]', 264 | 'bytes32[2]', 265 | 'bytes32[2]', 266 | 'bytes32[2]', 267 | 'bytes32[2]', 268 | ], [ 269 | bn128.bytes(x), 270 | bn128.serialize(A_y), 271 | bn128.serialize(A_D), 272 | bn128.serialize(A_b), 273 | bn128.serialize(A_X), 274 | bn128.serialize(A_t), 275 | bn128.serialize(A_u), 276 | ])); 277 | 278 | result.s_sk = k_sk.redAdd(result.c.redMul(witness['sk'])); 279 | result.s_r = k_r.redAdd(result.c.redMul(witness['r'])); 280 | result.s_b = k_b.redAdd(result.c.redMul(witness['bTransfer'].redMul(zs[0]).redAdd(witness['bDiff'].redMul(zs[1])).redMul(wPow))); 281 | result.s_tau = k_tau.redAdd(result.c.redMul(tauX.redMul(wPow))); 282 | 283 | const hOld = PedersenVectorCommitment.base['h']; 284 | const hsOld = PedersenVectorCommitment.base['hs']; 285 | const o = utils.hash(ABICoder.encodeParameters([ 286 | 'bytes32', 287 | ], [ 288 | bn128.bytes(result.c), 289 | ])); 290 | PedersenVectorCommitment.base['h'] = PedersenVectorCommitment.base['h'].mul(o); 291 | PedersenVectorCommitment.base['hs'] = PedersenVectorCommitment.base['hs'].hadamard(ys.invert()); 292 | 293 | const P = new PedersenVectorCommitment(bn128.zero); // P._commit(lPoly.evaluate(x), rPoly.evaluate(x), result.tHat); 294 | P.gValues = lPoly.evaluate(x); 295 | P.hValues = rPoly.evaluate(x); 296 | P.randomness = result.tHat; 297 | result.ipProof = InnerProductProof.prove(P, o); 298 | 299 | PedersenVectorCommitment.base['h'] = hOld; 300 | PedersenVectorCommitment.base['hs'] = hsOld; 301 | return result; 302 | } 303 | } 304 | 305 | module.exports = ZetherProof; -------------------------------------------------------------------------------- /packages/anonymous.js/src/utils/algebra.js: -------------------------------------------------------------------------------- 1 | const { soliditySha3 } = require('web3-utils'); 2 | const BN = require('bn.js'); 3 | 4 | const bn128 = require('../utils/bn128.js'); 5 | const utils = require('../utils/utils.js'); 6 | 7 | class PedersenCommitment { 8 | static base = { 9 | 'g': utils.mapInto(soliditySha3("G")), 10 | 'h': utils.mapInto(soliditySha3("H")), 11 | } 12 | 13 | constructor(point) { 14 | this._commit = (value, randomness) => { // both args are already-reduced BNs 15 | this.randomness = randomness; 16 | point = PedersenCommitment.base['g'].mul(value).add(PedersenCommitment.base['h'].mul(randomness)); 17 | }; 18 | this.point = () => point; 19 | } 20 | 21 | static commit(value) { // an already-reduced BN 22 | const result = new PedersenCommitment(bn128.zero); 23 | result._commit(value, bn128.randomScalar()); 24 | return result; 25 | } 26 | } 27 | class FieldVector { 28 | constructor(vector) { 29 | this.getVector = () => vector; 30 | this.length = () => vector.length; 31 | this.slice = (begin, end) => new FieldVector(vector.slice(begin, end)); 32 | this.flip = () => new FieldVector(Array.from({ length: this.length() }).map((_, i) => vector[(this.length() - i) % this.length()])); 33 | this.extract = (parity) => new FieldVector(vector.filter((_, i) => i % 2 === parity)); 34 | this.add = (other) => new FieldVector(other.getVector().map((elem, i) => vector[i].redAdd(elem))) 35 | this.negate = () => new FieldVector(vector.map((elem) => elem.redNeg())); 36 | this.plus = (constant) => new FieldVector(vector.map((elem) => elem.redAdd(constant))); 37 | this.push = (constant) => { vector.push(constant); }; 38 | this.sum = () => vector.reduce((accum, cur) => accum.redAdd(cur), new BN(0).toRed(bn128.q)); 39 | this.hadamard = (other) => new FieldVector(other.getVector().map((elem, i) => vector[i].redMul(elem))); 40 | this.invert = () => new FieldVector(vector.map((elem) => elem.redInvm())); 41 | this.times = (constant) => new FieldVector(vector.map((elem) => elem.redMul(constant))); 42 | this.innerProduct = (other) => other.getVector().reduce((accum, cur, i) => accum.redAdd(vector[i].redMul(cur)), new BN(0).toRed(bn128.q)); 43 | this.concat = (other) => new FieldVector(vector.concat(other.getVector())); 44 | } 45 | } 46 | 47 | class PointVector { 48 | constructor(vector) { 49 | this.getVector = () => vector; 50 | this.length = () => vector.length; 51 | this.slice = (begin, end) => new PointVector(vector.slice(begin, end)); 52 | this.flip = () => new PointVector(Array.from({ length: this.length() }).map((_, i) => vector[(this.length() - i) % this.length()])); 53 | this.extract = (parity) => new PointVector(vector.filter((_, i) => i % 2 === parity)); 54 | this.negate = () => new PointVector(vector.map((elem) => elem.neg())); 55 | this.multiExponentiate = (exponents) => exponents.getVector().reduce((accum, cur, i) => accum.add(vector[i].mul(cur)), bn128.zero); 56 | this.sum = () => vector.reduce((accum, cur) => accum.add(cur), bn128.zero); 57 | this.add = (other) => new PointVector(other.getVector().map((elem, i) => vector[i].add(elem))); 58 | this.hadamard = (exponents) => new PointVector(exponents.getVector().map((elem, i) => vector[i].mul(elem))); 59 | this.times = (constant) => new PointVector(vector.map((elem) => elem.mul(constant))); 60 | this.concat = (other) => new PointVector(vector.concat(other.getVector())); 61 | } 62 | } 63 | 64 | class ElGamalVector { 65 | constructor(vector) { 66 | this.getVector = () => vector; 67 | this.multiExponentiate = (exponents) => exponents.getVector().reduce((accum, cur, i) => accum.add(vector[i].mul(cur)), new ElGamal(bn128.zero, bn128.zero)); 68 | this.sum = () => vector.reduce((accum, cur) => accum.add(cur), new ElGamal(bn128.zero, bn128.zero)); 69 | this.add = (other) => new ElGamalVector(other.getVector().map((elem, i) => vector[i].add(elem))); 70 | this.hadamard = (exponents) => new ElGamalVector(exponents.getVector().map((elem, i) => vector[i].mul(elem))); 71 | this.times = (constant) => new ElGamalVector(vector.map((elem) => elem.mul(constant))); 72 | } 73 | } 74 | 75 | class PedersenVectorCommitment { 76 | static base = { // hardcode length 64 for zether 77 | 'gs': new PointVector(Array.from({ 'length': 64 }).map((_, i) => utils.mapInto(soliditySha3("G", i)))), 78 | 'hs': new PointVector(Array.from({ 'length': 64 }).map((_, i) => utils.mapInto(soliditySha3("H", i)))), 79 | 'h': utils.mapInto(soliditySha3("H")), 80 | }; 81 | static sum = PedersenVectorCommitment.base['gs'].sum(); // KLUDGY. 82 | 83 | constructor(point) { 84 | this._commit = (gValues, hValues, randomness) => { // first args of type FieldVector?! 85 | this.gValues = gValues; 86 | this.hValues = hValues; 87 | this.randomness = randomness; 88 | point = PedersenVectorCommitment.base['h'].mul(randomness); 89 | point = point.add(PedersenVectorCommitment.base['gs'].multiExponentiate(gValues)); 90 | point = point.add(PedersenVectorCommitment.base['hs'].multiExponentiate(hValues)); 91 | }; 92 | this.point = () => point; 93 | } 94 | 95 | static commit(gValues, hValues) { // vectors of already-reduced BNs 96 | const result = new PedersenVectorCommitment(bn128.zero); 97 | result._commit(gValues, hValues, bn128.randomScalar()); 98 | return result; 99 | } 100 | } 101 | 102 | class ElGamal { 103 | static base = { 104 | 'g': PedersenCommitment.base['g'], // only used for messages. 105 | } 106 | 107 | constructor(left, right) { 108 | this._commit = (key, value, randomness) => { 109 | this.randomness = randomness; 110 | left = ElGamal.base['g'].mul(value).add(key.mul(randomness)); 111 | right = bn128.curve.g.mul(randomness); 112 | }; 113 | this.left = () => left 114 | this.right = () => right; 115 | this.zero = () => left.eq(bn128.zero) && right.eq(bn128.zero); 116 | this.add = (other) => new ElGamal(left === undefined ? undefined : left.add(other.left()), right.add(other.right())); 117 | this.mul = (scalar) => new ElGamal(left.mul(scalar), right.mul(scalar)); 118 | this.plus = (constant) => new ElGamal(left.add(ElGamal.base['g'].mul(constant)), right); // affine 119 | this.neg = () => new ElGamal(left.neg(), right.neg()); 120 | } 121 | 122 | static commit(key, value) { // value is a BN; we will exponentiate. 123 | const result = new ElGamal(bn128.zero, bn128.zero); 124 | result._commit(key, value, bn128.randomScalar()); 125 | return result; 126 | } 127 | 128 | static deserialize(account) { 129 | return new ElGamal(bn128.deserialize(account[0]), bn128.deserialize(account[1])); 130 | } 131 | } 132 | 133 | module.exports = { PedersenCommitment, PedersenVectorCommitment, ElGamal, FieldVector, PointVector, ElGamalVector }; -------------------------------------------------------------------------------- /packages/anonymous.js/src/utils/bn128.js: -------------------------------------------------------------------------------- 1 | const BN = require('bn.js') 2 | const EC = require('elliptic') 3 | const crypto = require('crypto') 4 | 5 | const FIELD_MODULUS = new BN("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", 16); 6 | const GROUP_MODULUS = new BN("30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", 16); 7 | const B_MAX = 4294967295; 8 | const empty = "0x0000000000000000000000000000000000000000000000000000000000000000"; 9 | const bn128 = {}; 10 | 11 | bn128.curve = new EC.curve.short({ 12 | a: '0', 13 | b: '3', 14 | p: FIELD_MODULUS, 15 | n: GROUP_MODULUS, 16 | gRed: false, 17 | g: ['077da99d806abd13c9f15ece5398525119d11e11e9836b2ee7d23f6159ad87d4', '01485efa927f2ad41bff567eec88f32fb0a0f706588b4e41a8d587d008b7f875'], 18 | // bizarre that g is set equal to one of the pedersen base elements. actually in theory not necessary (though the verifier would have to change also). 19 | }); 20 | 21 | bn128.zero = bn128.curve.g.mul(0); 22 | 23 | bn128.p = BN.red(bn128.curve.p); 24 | bn128.q = BN.red(bn128.curve.n); 25 | 26 | bn128.randomScalar = () => new BN(crypto.randomBytes(32), 16).toRed(bn128.q); 27 | bn128.bytes = (i) => "0x" + i.toString(16, 64); 28 | bn128.serialize = (point) => { // differs from point.encode('hex'). ethereum-compatible 29 | if (point.x === null && point.y === null) return [empty, empty]; 30 | return [bn128.bytes(point.getX()), bn128.bytes(point.getY())]; 31 | }; 32 | bn128.representation = (point) => { // essentially for serializing proofs... 33 | if (point.x === null && point.y === null) return empty + empty.slice(2); 34 | return bn128.bytes(point.getX()) + bn128.bytes(point.getY()).slice(2); 35 | }; 36 | bn128.deserialize = (serialization) => { 37 | if (serialization[0] === empty && serialization[1] === empty) return bn128.zero; 38 | return bn128.curve.point(serialization[0].slice(2), serialization[1].slice(2)); // no check if valid curve point? 39 | }; 40 | 41 | bn128.B_MAX = B_MAX; 42 | 43 | module.exports = bn128; -------------------------------------------------------------------------------- /packages/anonymous.js/src/utils/misc.js: -------------------------------------------------------------------------------- 1 | const bn128 = require('../utils/bn128.js'); 2 | const BN = require('bn.js'); 3 | 4 | const { FieldVector } = require('./algebra.js'); 5 | 6 | class Polynomial { 7 | constructor(coefficients) { 8 | this.coefficients = coefficients; // vector of coefficients, _little_ endian. 9 | this.mul = (other) => { // i assume that other has coeffs.length == 2, and monic if linear. 10 | const product = this.coefficients.map((coefficient) => coefficient.redMul(other.coefficients[0])); 11 | product.push(new BN(0).toRed(bn128.q)); 12 | if (other.coefficients[1].eqn(1)) this.coefficients.forEach((elem, i) => product[i + 1] = product[i + 1].redAdd(elem)); 13 | return new Polynomial(product); 14 | } 15 | } 16 | } 17 | 18 | class FieldVectorPolynomial { 19 | constructor(...coefficients) { // an array of fieldvectors (2 in practice, but could be arbitrary). 20 | this.getCoefficients = () => coefficients; 21 | 22 | this.evaluate = (x) => { 23 | let result = coefficients[0]; 24 | let accumulator = x; 25 | coefficients.slice(1).forEach((coefficient) => { 26 | result = result.add(coefficient.times(accumulator)); 27 | accumulator = accumulator.redMul(x); 28 | }); 29 | return result; 30 | }; 31 | 32 | this.innerProduct = (other) => { 33 | const result = Array(coefficients.length + other.getCoefficients().length - 1).fill(new BN(0).toRed(bn128.q)); 34 | other.getCoefficients().forEach((theirs, i) => { 35 | coefficients.forEach((mine, j) => { 36 | result[i + j] = result[i + j].redAdd(mine.innerProduct(theirs)); 37 | }); 38 | }); 39 | return result; // just a plain array? 40 | }; 41 | } 42 | } 43 | 44 | class Convolver { 45 | static unity = new BN("14a3074b02521e3b1ed9852e5028452693e87be4e910500c7ba9bbddb2f46edd", 16).toRed(bn128.q); // can it be both static and const? 46 | static unity_inv = new BN("26e5d943cd2c53aced15060255c58a5581bb6108161239002a021f09b39972c9", 16).toRed(bn128.q); 47 | static two_inv = new BN("183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000001", 16).toRed(bn128.q); 48 | 49 | constructor() { 50 | let base_fft; 51 | let omegas; 52 | let inverses; 53 | 54 | const fft = (input, omegas, inverse) => { // crazy... i guess this will work for both points and scalars? 55 | const size = input.length(); 56 | if (size === 1) return input; 57 | if (size % 2 !== 0) throw "Input size must be a power of 2!"; 58 | const even = fft(input.extract(0), omegas.extract(0), inverse); 59 | const odd = fft(input.extract(1), omegas.extract(0), inverse); 60 | const temp = odd.hadamard(omegas); 61 | let result = even.add(temp).concat(even.add(temp.negate())); 62 | if (inverse) result = result.times(Convolver.two_inv); 63 | return result; 64 | }; 65 | 66 | this.prepare = (base) => { 67 | const size = base.length(); 68 | const omega = Convolver.unity.redPow(new BN(1).shln(28).div(new BN(size))); // can i right-shift? 69 | const omega_inv = Convolver.unity_inv.redPow(new BN(1).shln(28).div(new BN(size / 2))); // can i right-shift? 70 | omegas = new FieldVector([new BN(1).toRed(bn128.q)]); 71 | inverses = new FieldVector([new BN(1).toRed(bn128.q)]); 72 | for (let i = 1; i < size / 2; i++) omegas.push(omegas.getVector()[i - 1].redMul(omega)); 73 | for (let i = 1; i < size / 4; i++) inverses.push(inverses.getVector()[i - 1].redMul(omega_inv)); 74 | base_fft = fft(base, omegas, false); 75 | }; 76 | 77 | this.convolution = (exponent) => { // returns only even-indexed outputs of convolution! 78 | const size = exponent.length(); 79 | const temp = base_fft.hadamard(fft(exponent.flip(), omegas, false)); 80 | return fft(temp.slice(0, size / 2).add(temp.slice(size / 2)).times(Convolver.two_inv), inverses, true); 81 | }; // using the optimization described here https://dsp.stackexchange.com/a/30699 82 | } 83 | } 84 | 85 | module.exports = { Polynomial, FieldVectorPolynomial, Convolver }; -------------------------------------------------------------------------------- /packages/anonymous.js/src/utils/service.js: -------------------------------------------------------------------------------- 1 | const ZetherProof = require('../prover/zether.js'); 2 | const BurnProof = require('../prover/burn.js'); 3 | 4 | class Service { 5 | static proveTransfer(Cn, C, y, epoch, sk, r, bTransfer, bDiff, index, fee) { 6 | const statement = {}; 7 | statement['Cn'] = Cn; 8 | statement['C'] = C; 9 | statement['y'] = y; 10 | statement['epoch'] = epoch; 11 | 12 | const witness = {}; 13 | witness['sk'] = sk; 14 | witness['r'] = r; 15 | witness['bTransfer'] = bTransfer; 16 | witness['bDiff'] = bDiff; 17 | witness['index'] = index; 18 | 19 | return ZetherProof.prove(statement, witness, fee); 20 | }; 21 | 22 | static proveBurn(Cn, y, epoch, sender, sk, bDiff) { 23 | const statement = {}; 24 | statement['Cn'] = Cn; 25 | statement['y'] = y; 26 | statement['epoch'] = epoch; 27 | statement['sender'] = sender; 28 | 29 | const witness = {}; 30 | witness['sk'] = sk; 31 | witness['bDiff'] = bDiff; 32 | 33 | return BurnProof.prove(statement, witness); 34 | } 35 | } 36 | 37 | module.exports = Service; -------------------------------------------------------------------------------- /packages/anonymous.js/src/utils/utils.js: -------------------------------------------------------------------------------- 1 | const bn128 = require('./bn128.js') 2 | const BN = require('bn.js') 3 | const { soliditySha3 } = require('web3-utils'); 4 | const ABICoder = require('web3-eth-abi'); 5 | 6 | const utils = {}; 7 | 8 | utils.sign = (address, keypair) => { 9 | const k = bn128.randomScalar(); 10 | const K = bn128.curve.g.mul(k); 11 | const c = utils.hash(ABICoder.encodeParameters([ 12 | 'address', 13 | 'bytes32[2]', 14 | 'bytes32[2]', 15 | ], [ 16 | address, 17 | bn128.serialize(keypair['y']), 18 | bn128.serialize(K), 19 | ])); 20 | 21 | const s = c.redMul(keypair['x']).redAdd(k); 22 | return [bn128.bytes(c), bn128.bytes(s)]; 23 | } 24 | 25 | utils.createAccount = () => { 26 | const x = bn128.randomScalar(); 27 | const y = bn128.curve.g.mul(x); 28 | return { 'x': x, 'y': y }; 29 | }; 30 | 31 | utils.readBalance = (CL, CR, x) => { 32 | CL = bn128.deserialize(CL); 33 | CR = bn128.deserialize(CR); 34 | const gB = CL.add(CR.mul(x.redNeg())); 35 | 36 | let accumulator = bn128.zero; 37 | for (let i = 0; i < bn128.B_MAX; i++) { 38 | if (accumulator.eq(gB)) return i; 39 | accumulator = accumulator.add(bn128.curve.g); 40 | } 41 | }; 42 | 43 | utils.mapInto = (seed) => { // seed is flattened 0x + hex string 44 | const seed_red = new BN(seed.slice(2), 16).toRed(bn128.p); 45 | const p_1_4 = bn128.curve.p.add(new BN(1)).div(new BN(4)); 46 | while (true) { 47 | const y_squared = seed_red.redPow(new BN(3)).redAdd(new BN(3).toRed(bn128.p)); 48 | const y = y_squared.redPow(p_1_4); 49 | if (y.redPow(new BN(2)).eq(y_squared)) { 50 | return bn128.curve.point(seed_red.fromRed(), y.fromRed()); 51 | } 52 | seed_red.redIAdd(new BN(1).toRed(bn128.p)); 53 | } 54 | }; 55 | 56 | utils.gEpoch = (epoch) => { 57 | return utils.mapInto(soliditySha3("Zether", epoch)); 58 | }; 59 | 60 | utils.u = (epoch, x) => { 61 | return utils.gEpoch(epoch).mul(x); 62 | }; 63 | 64 | utils.hash = (encoded) => { // ags are serialized 65 | return new BN(soliditySha3(encoded).slice(2), 16).toRed(bn128.q); 66 | }; 67 | 68 | module.exports = utils; -------------------------------------------------------------------------------- /packages/protocol/contracts/BurnVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License 2.0 2 | pragma solidity ^0.7.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./Utils.sol"; 6 | import "./InnerProductVerifier.sol"; 7 | 8 | contract BurnVerifier { 9 | using Utils for uint256; 10 | using Utils for Utils.G1Point; 11 | 12 | InnerProductVerifier ip; 13 | 14 | struct BurnStatement { 15 | Utils.G1Point CLn; 16 | Utils.G1Point CRn; 17 | Utils.G1Point y; 18 | uint256 epoch; // or uint8? 19 | address sender; 20 | Utils.G1Point u; 21 | } 22 | 23 | struct BurnProof { 24 | Utils.G1Point BA; 25 | Utils.G1Point BS; 26 | 27 | Utils.G1Point T_1; 28 | Utils.G1Point T_2; 29 | uint256 tHat; 30 | uint256 mu; 31 | 32 | uint256 c; 33 | uint256 s_sk; 34 | uint256 s_b; 35 | uint256 s_tau; 36 | 37 | InnerProductVerifier.InnerProductProof ipProof; 38 | } 39 | 40 | constructor(address _ip) { 41 | ip = InnerProductVerifier(_ip); 42 | } 43 | 44 | function verifyBurn(Utils.G1Point memory CLn, Utils.G1Point memory CRn, Utils.G1Point memory y, uint256 epoch, Utils.G1Point memory u, address sender, bytes memory proof) public view returns (bool) { 45 | BurnStatement memory statement; // WARNING: if this is called directly in the console, 46 | // and your strings are less than 64 characters, they will be padded on the right, not the left. should hopefully not be an issue, 47 | // as this will typically be called simply by the other contract. still though, beware 48 | statement.CLn = CLn; 49 | statement.CRn = CRn; 50 | statement.y = y; 51 | statement.epoch = epoch; 52 | statement.u = u; 53 | statement.sender = sender; 54 | BurnProof memory burnProof = unserialize(proof); 55 | return verify(statement, burnProof); 56 | } 57 | 58 | struct BurnAuxiliaries { 59 | uint256 y; 60 | uint256[32] ys; 61 | uint256 z; 62 | uint256[1] zs; // silly. just to match zether. 63 | uint256 zSum; 64 | uint256[32] twoTimesZSquared; 65 | uint256 x; 66 | uint256 t; 67 | uint256 k; 68 | Utils.G1Point tEval; 69 | } 70 | 71 | struct SigmaAuxiliaries { 72 | uint256 c; 73 | Utils.G1Point A_y; 74 | Utils.G1Point A_b; 75 | Utils.G1Point A_t; 76 | Utils.G1Point gEpoch; 77 | Utils.G1Point A_u; 78 | } 79 | 80 | struct IPAuxiliaries { 81 | Utils.G1Point P; 82 | Utils.G1Point u_x; 83 | Utils.G1Point[] hPrimes; 84 | Utils.G1Point hPrimeSum; 85 | uint256 o; 86 | } 87 | 88 | function gSum() internal pure returns (Utils.G1Point memory) { 89 | return Utils.G1Point(0x2257118d30fe5064dda298b2fac15cf96fd51f0e7e3df342d0aed40b8d7bb151, 0x0d4250e7509c99370e6b15ebfe4f1aa5e65a691133357901aa4b0641f96c80a8); 90 | } 91 | 92 | function verify(BurnStatement memory statement, BurnProof memory proof) internal view returns (bool) { 93 | uint256 statementHash = uint256(keccak256(abi.encode(statement.CLn, statement.CRn, statement.y, statement.epoch, statement.sender))).mod(); // stacktoodeep? 94 | 95 | BurnAuxiliaries memory burnAuxiliaries; 96 | burnAuxiliaries.y = uint256(keccak256(abi.encode(statementHash, proof.BA, proof.BS))).mod(); 97 | burnAuxiliaries.ys[0] = 1; 98 | burnAuxiliaries.k = 1; 99 | for (uint256 i = 1; i < 32; i++) { 100 | burnAuxiliaries.ys[i] = burnAuxiliaries.ys[i - 1].mul(burnAuxiliaries.y); 101 | burnAuxiliaries.k = burnAuxiliaries.k.add(burnAuxiliaries.ys[i]); 102 | } 103 | burnAuxiliaries.z = uint256(keccak256(abi.encode(burnAuxiliaries.y))).mod(); 104 | burnAuxiliaries.zs[0] = burnAuxiliaries.z.mul(burnAuxiliaries.z); 105 | burnAuxiliaries.zSum = burnAuxiliaries.zs[0].mul(burnAuxiliaries.z); // trivial sum 106 | burnAuxiliaries.k = burnAuxiliaries.k.mul(burnAuxiliaries.z.sub(burnAuxiliaries.zs[0])).sub(burnAuxiliaries.zSum.mul(1 << 32).sub(burnAuxiliaries.zSum)); 107 | burnAuxiliaries.t = proof.tHat.sub(burnAuxiliaries.k); 108 | for (uint256 i = 0; i < 32; i++) { 109 | burnAuxiliaries.twoTimesZSquared[i] = burnAuxiliaries.zs[0].mul(1 << i); 110 | } 111 | 112 | burnAuxiliaries.x = uint256(keccak256(abi.encode(burnAuxiliaries.z, proof.T_1, proof.T_2))).mod(); 113 | burnAuxiliaries.tEval = proof.T_1.mul(burnAuxiliaries.x).add(proof.T_2.mul(burnAuxiliaries.x.mul(burnAuxiliaries.x))); // replace with "commit"? 114 | 115 | SigmaAuxiliaries memory sigmaAuxiliaries; 116 | sigmaAuxiliaries.A_y = Utils.g().mul(proof.s_sk).add(statement.y.mul(proof.c.neg())); 117 | sigmaAuxiliaries.A_b = Utils.g().mul(proof.s_b).add(statement.CRn.mul(proof.s_sk).add(statement.CLn.mul(proof.c.neg())).mul(burnAuxiliaries.zs[0])); 118 | sigmaAuxiliaries.A_t = Utils.g().mul(burnAuxiliaries.t).add(burnAuxiliaries.tEval.neg()).mul(proof.c).add(Utils.h().mul(proof.s_tau)).add(Utils.g().mul(proof.s_b.neg())); 119 | sigmaAuxiliaries.gEpoch = Utils.mapInto("Zether", statement.epoch); 120 | sigmaAuxiliaries.A_u = sigmaAuxiliaries.gEpoch.mul(proof.s_sk).add(statement.u.mul(proof.c.neg())); 121 | 122 | sigmaAuxiliaries.c = uint256(keccak256(abi.encode(burnAuxiliaries.x, sigmaAuxiliaries.A_y, sigmaAuxiliaries.A_b, sigmaAuxiliaries.A_t, sigmaAuxiliaries.A_u))).mod(); 123 | require(sigmaAuxiliaries.c == proof.c, "Sigma protocol challenge equality failure."); 124 | 125 | IPAuxiliaries memory ipAuxiliaries; 126 | ipAuxiliaries.o = uint256(keccak256(abi.encode(sigmaAuxiliaries.c))).mod(); 127 | ipAuxiliaries.u_x = Utils.h().mul(ipAuxiliaries.o); 128 | ipAuxiliaries.hPrimes = new Utils.G1Point[](32); 129 | for (uint256 i = 0; i < 32; i++) { 130 | ipAuxiliaries.hPrimes[i] = ip.hs(i).mul(burnAuxiliaries.ys[i].inv()); 131 | ipAuxiliaries.hPrimeSum = ipAuxiliaries.hPrimeSum.add(ipAuxiliaries.hPrimes[i].mul(burnAuxiliaries.ys[i].mul(burnAuxiliaries.z).add(burnAuxiliaries.twoTimesZSquared[i]))); 132 | } 133 | ipAuxiliaries.P = proof.BA.add(proof.BS.mul(burnAuxiliaries.x)).add(gSum().mul(burnAuxiliaries.z.neg())).add(ipAuxiliaries.hPrimeSum); 134 | ipAuxiliaries.P = ipAuxiliaries.P.add(Utils.h().mul(proof.mu.neg())); 135 | ipAuxiliaries.P = ipAuxiliaries.P.add(ipAuxiliaries.u_x.mul(proof.tHat)); 136 | require(ip.verifyInnerProduct(ipAuxiliaries.hPrimes, ipAuxiliaries.u_x, ipAuxiliaries.P, proof.ipProof, ipAuxiliaries.o), "Inner product proof verification failed."); 137 | 138 | return true; 139 | } 140 | 141 | function unserialize(bytes memory arr) internal pure returns (BurnProof memory proof) { 142 | proof.BA = Utils.G1Point(Utils.slice(arr, 0), Utils.slice(arr, 32)); 143 | proof.BS = Utils.G1Point(Utils.slice(arr, 64), Utils.slice(arr, 96)); 144 | 145 | proof.T_1 = Utils.G1Point(Utils.slice(arr, 128), Utils.slice(arr, 160)); 146 | proof.T_2 = Utils.G1Point(Utils.slice(arr, 192), Utils.slice(arr, 224)); 147 | proof.tHat = uint256(Utils.slice(arr, 256)); 148 | proof.mu = uint256(Utils.slice(arr, 288)); 149 | 150 | proof.c = uint256(Utils.slice(arr, 320)); 151 | proof.s_sk = uint256(Utils.slice(arr, 352)); 152 | proof.s_b = uint256(Utils.slice(arr, 384)); 153 | proof.s_tau = uint256(Utils.slice(arr, 416)); 154 | 155 | InnerProductVerifier.InnerProductProof memory ipProof; 156 | ipProof.L = new Utils.G1Point[](5); 157 | ipProof.R = new Utils.G1Point[](5); 158 | for (uint256 i = 0; i < 5; i++) { // 2^5 = 32. 159 | ipProof.L[i] = Utils.G1Point(Utils.slice(arr, 448 + i * 64), Utils.slice(arr, 480 + i * 64)); 160 | ipProof.R[i] = Utils.G1Point(Utils.slice(arr, 448 + (5 + i) * 64), Utils.slice(arr, 480 + (5 + i) * 64)); 161 | } 162 | ipProof.a = uint256(Utils.slice(arr, 448 + 5 * 128)); 163 | ipProof.b = uint256(Utils.slice(arr, 480 + 5 * 128)); 164 | proof.ipProof = ipProof; 165 | 166 | return proof; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /packages/protocol/contracts/CashToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License 2.0 2 | pragma solidity ^0.7.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract CashToken is ERC20 { 7 | constructor () ERC20("Anonymous Zether", "ZTH") { 8 | // etc 9 | } 10 | 11 | function mint(address account, uint256 amount) external { // just for testing---expose the method. 12 | _mint(account, amount); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/protocol/contracts/InnerProductVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License 2.0 2 | pragma solidity ^0.7.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./Utils.sol"; 6 | 7 | contract InnerProductVerifier { 8 | using Utils for uint256; 9 | using Utils for Utils.G1Point; 10 | 11 | struct InnerProductStatement { 12 | Utils.G1Point[] hs; // "overridden" parameters. 13 | Utils.G1Point u; 14 | Utils.G1Point P; 15 | } 16 | 17 | struct InnerProductProof { 18 | Utils.G1Point[] L; 19 | Utils.G1Point[] R; 20 | uint256 a; 21 | uint256 b; 22 | } 23 | 24 | function verifyInnerProduct(Utils.G1Point[] memory hs, Utils.G1Point memory u, Utils.G1Point memory P, InnerProductProof memory proof, uint256 salt) public view returns (bool) { 25 | InnerProductStatement memory statement; 26 | statement.hs = hs; 27 | statement.u = u; 28 | statement.P = P; 29 | 30 | return verify(statement, proof, salt); 31 | } 32 | 33 | function gs(uint256 i) public pure returns (Utils.G1Point memory) { 34 | if (i == 0) return Utils.G1Point(0x0d1fff31f8dfb29333568b00628a0f92a752e8dee420dfede1be731810a807b9, 0x06c3001c74387dae9deddc75b76959ef5f98f1be48b0d9fc8ff6d7d76106b41b); 35 | if (i == 1) return Utils.G1Point(0x06e1b58cb1420e3d12020c5be2c4e48955efc64310ab10002164d0e2a767018e, 0x229facdebea78bd67f5b332bcdab7d692d0c4b18d77e92a8b3ffaee450c797c7); 36 | if (i == 2) return Utils.G1Point(0x22f32c65b43f3e770b793ea6e31c85d1aea2c41ea3204fc08a036004e5adef3a, 0x1d63e3737f864f05f62e2be0a6b7528b76cdabcda9703edc304c015480fb5543); 37 | if (i == 3) return Utils.G1Point(0x01df5e3e2818cfce850bd5d5f57872abc34b1315748e0280c4f0d3d6a40f94a9, 0x0d622581880ddba6a3911aa0df64f4fd816800c6dee483f07aa542a6e61534d5); 38 | if (i == 4) return Utils.G1Point(0x18d7f2117b1144f5035218384d817c6d1b4359497489a52bcf9d16c44624c1d0, 0x115f00d2f27917b5a3e8e6754451a4e990931516cf47e742949b8cbdda0e2c20); 39 | if (i == 5) return Utils.G1Point(0x093a9e9ba588d1b8eae48cf96b97def1fb8dccd519678520314e96d289ad1d11, 0x0f94a152edd0254ece896bc7e56708ba623c1ed3a27e4fd4c449f8e98fee1b5e); 40 | if (i == 6) return Utils.G1Point(0x0a7e8bc3cecaff1d9ec3e7d9c1fab7b5397bd6b6739c99bfe4bcb21d08d25934, 0x18d0114fa64774f712044e9a05b818fea4734db2b91fc7f049e120ce01c096be); 41 | if (i == 7) return Utils.G1Point(0x2095c16aea6e127aa3394d0124b545a45323708ae1c227575270d99b9900673a, 0x24c5a6afc36ef443197217591e084cdd69820401447163b5ab5f015801551a03); 42 | if (i == 8) return Utils.G1Point(0x041ee7d5aa6e191ba063876fda64b87728fa3ed39531400118b83372cbb5af75, 0x2dc2abc7d618ae4e1522f90d294c23627b6bc4f60093e8f07a7cd3869dac9836); 43 | if (i == 9) return Utils.G1Point(0x16dc75831b780dc5806dd5b8973f57f2f4ce8ad2a6bb152fbd9ccb58534115b4, 0x17b434c3b65a2f754c99f7bacf2f20bdcd7517a38e5eb301d2d88fe7735ebc9c); 44 | if (i == 10) return Utils.G1Point(0x18f1393a76e0af102ffeb380787ed950dc35b04b0cc6de1a6d806d4007b30dba, 0x1d640e43bab253bf176b69dffdb3ffc02640c591c392f400596155c8c3f668ef); 45 | if (i == 11) return Utils.G1Point(0x2bf3f58b4c957a8ae697aa57eb3f7428527fcb0c7e8d099efae80b97bde600e0, 0x14072f8bfdbe285b203cd0a2ebc1aed9ad1de309794226aee63c89397b187abf); 46 | if (i == 12) return Utils.G1Point(0x028eb6852c2827302aeb09def685b57bef74ff1a3ff72eda972e32b9ea80c32f, 0x1ba2dfb85a585de4b8a189f7b764f87c6f8e06c10d68d4493fc469504888837d); 47 | if (i == 13) return Utils.G1Point(0x19003e6b8f14f3583435527eac51a460c705dc6a042a2b7dd56b4f598af50886, 0x10e755ac3373f769e7e092f9eca276d911cd31833e82c70b8af09787e2c02d20); 48 | if (i == 14) return Utils.G1Point(0x0d493d4d49aa1a4fdf3bc3ba6d969b3e203741b3d570dbc511dd3171baf96f85, 0x1d103731795bcc57ddb8514e0e232446bfd9834f6a8ae9ff5235330d2a9e5ffa); 49 | if (i == 15) return Utils.G1Point(0x0ce438e766aae8c59b4006ee1749f40370fe5ec9fe29edce6b98e945915db97f, 0x02dba20dff83b373d2b47282e08d2c7883254a56701f2dbeea7ccc167ffb49a5); 50 | if (i == 16) return Utils.G1Point(0x05092110319650610a94fa0f9d50536404ba526380fc31b99ce95fbc1423a26f, 0x18a40146a4e79c2830d6d6e56314c538b0da4a2a72b7533e63f7d0a7e5ab2d22); 51 | if (i == 17) return Utils.G1Point(0x25b9ad9c4235b0a2e9f1b2ed20a5ca63814e1fb0eb95540c6f4f163c1a9fc2bd, 0x0a726ff7b655ad45468bcfd2d77f8aa0786ff3012d4edb77b5118f863dcdcbc0); 52 | if (i == 18) return Utils.G1Point(0x291ff28fa0a9840e230de0f0da725900bd18ce31d2369ffc80abbc4a77c1aff3, 0x1ffed5e9dffcd885ac867e2279836a11225548a8c253c47efe24f7d95a4bdd61); 53 | if (i == 19) return Utils.G1Point(0x0a01c96340d6bb4c94e028a522f74bef899d8f9d1a6d0b0d832f83275efa68de, 0x119c6a17ecb14721ac9eb331abccf2748868855fae43392391c37037d1b150a1); 54 | if (i == 20) return Utils.G1Point(0x2c846ad384d3ea063001f34fd60f0b8dc12b3b3ab7a5757f1d394f19850d8309, 0x1ff69942134c51e7315ccf1431e66fb5f70c24148c668f4fbe3861fbe535e39c); 55 | if (i == 21) return Utils.G1Point(0x0dafb5ae6accb6048e6dbc52f455c262dd2876b565792d68189618a3e630ade0, 0x236e97c592c19a2f2244f2938021671045787501e5a4a26de3580628ce37eb3b); 56 | if (i == 22) return Utils.G1Point(0x10df3e10a8d613058eae3278e2c80c3366c482354260f501447d15797de7378a, 0x10b25f7e075c93203ceba523afc44e0d5cd9e45a60b6dc11d2034180c40a004d); 57 | if (i == 23) return Utils.G1Point(0x1437b718d075d54da65adccdd3b6f758a5b76a9e5c5c7a13bf897a92e23fcde2, 0x0f0b988d70298608d02c73c410dc8b8bb6b95f0dde0dedcd5ea5692f0c07f3ed); 58 | if (i == 24) return Utils.G1Point(0x2705c71a95661231956d10845933f43cd973f4626e3a31dbf6287e01a00beb70, 0x27d09bd21d44269e2e7c85e1555fd351698eca14686d5aa969cb08e33db6691b); 59 | if (i == 25) return Utils.G1Point(0x1614dabf48099c315f244f8763f4b99ca2cef559781bf55e8e4d912d952edb4a, 0x16bf2f8fb1021b47be88ceb6fce08bf3b3a17026509cf9756c1a3fbf3b9d70bd); 60 | if (i == 26) return Utils.G1Point(0x21c448cfdcf007959812b2c5977cd4a808fa25408547e660c3fc12ed47501eb3, 0x14495c361cf9dc10222549bc258a76a20058f4795c2e65cd27f013c940b7dc7b); 61 | if (i == 27) return Utils.G1Point(0x1ac35f37ee0bfcb173d513ea7ac1daf5b46c6f70ce5f82a0396e7afac270ff35, 0x2f5f4480260b838ffcba9d34396fc116f75d1d5c24396ed4f7e01fd010ab9970); 62 | if (i == 28) return Utils.G1Point(0x0caaa12a18563703797d9be6ef74cbfb9e532cd027a1021f34ad337ce231e074, 0x2281c11389906c02bb15e995ffd6db136c3cdb4ec0829b88aec6db8dda05d5af); 63 | if (i == 29) return Utils.G1Point(0x1f3d91f1dfbbf01002a7e339ff6754b4ad2290493757475a062a75ec44bc3d50, 0x207b99884d9f7ca1e2f04457b90982ec6f8fb0a5b2ffd5b50d9cf4b2d850a920); 64 | if (i == 30) return Utils.G1Point(0x1fe58e4e4b1d155fb0a97dc9bae46f401edb2828dc4f96dafb86124cba424455, 0x01ad0a57feb7eeda4319a70ea56ded5e9fef71c78ff84413399d51f647d55113); 65 | if (i == 31) return Utils.G1Point(0x044e80195798557e870554d7025a8bc6b2ee9a05fa6ae016c3ab3b9e97af5769, 0x2c141a12135c4d14352fc60d851cdde147270f76405291b7c5d01da8f5dfed4d); 66 | if (i == 32) return Utils.G1Point(0x2883d31d84e605c858cf52260183f09d18bd55dc330f8bf12785e7a2563f8da4, 0x0e681e5c997f0bb609af7a95f920f23c4be78ded534832b514510518ede888b2); 67 | if (i == 33) return Utils.G1Point(0x2cdf5738c2690b263dfdc2b4235620d781bbff534d3363c4f3cfe5d1c67767c1, 0x15f4fb05e5facfd1988d61fd174a14b20e1dbe6ac37946e1527261be8742f5cf); 68 | if (i == 34) return Utils.G1Point(0x05542337765c24871e053bb8ec4e1baaca722f58b834426431c6d773788e9c66, 0x00e64d379c28d138d394f2cf9f0cc0b5a71e93a055bad23a2c6de74b217f3fac); 69 | if (i == 35) return Utils.G1Point(0x2efe9c1359531adb8a104242559a320593803c89a6ff0c6c493d7da5832603ab, 0x295898b3b86cf9e09e99d7f80e539078d3b5455bba60a5aa138b2995b75f0409); 70 | if (i == 36) return Utils.G1Point(0x2a3740ca39e35d23a5107fdae38209eaebdcd70ae740c873caf8b0b64d92db31, 0x05bab66121bccf807b1f776dc487057a5adf5f5791019996a2b7a2dbe1488797); 71 | if (i == 37) return Utils.G1Point(0x11ef5ef35b895540be39974ac6ad6697ef4337377f06092b6a668062bf0d8019, 0x1a42e3b4b73119a4be1dde36a8eaf553e88717cecb3fdfdc65ed2e728fda0782); 72 | if (i == 38) return Utils.G1Point(0x245aac96c5353f38ae92c6c17120e123c223b7eaca134658ebf584a8580ec096, 0x25ec55531155156663f8ba825a78f41f158def7b9d082e80259958277369ed08); 73 | if (i == 39) return Utils.G1Point(0x0fb13a72db572b1727954bb77d014894e972d7872678200a088febe8bd949986, 0x151af2ae374e02dec2b8c5dbde722ae7838d70ab4fd0857597b616a96a1db57c); 74 | if (i == 40) return Utils.G1Point(0x155fa64e4c8bf5f5aa53c1f5e44d961f688132c8545323d3bdc6c43a83220f89, 0x188507b59213816846bc9c763a93b52fb7ae8e8c8cc7549ce3358728415338a4); 75 | if (i == 41) return Utils.G1Point(0x28631525d5192140fd4fb04efbad8dcfddd5b8d0f5dc54442e5530989ef5b7fe, 0x0ad3a3d4845b4bc6a92563e72db2bc836168a295c56987c7bb1eea131a3760ac); 76 | if (i == 42) return Utils.G1Point(0x043b2963b1c5af8e2e77dfb89db7a0d907a40180929f3fd630a4a37811030b6d, 0x0721a4b292b41a3d948237bf076aabeedba377c43a10f78f368042ad155a3c91); 77 | if (i == 43) return Utils.G1Point(0x14bfb894e332921cf925f726f7c242a70dbd9366b68b50e14b618a86ecd45bd6, 0x09b1c50016fff7018a9483ce00b8ec3b6a0df36db21ae3b8282ca0b4be2e283c); 78 | if (i == 44) return Utils.G1Point(0x2758e65c03fdb27e58eb300bde8ada18372aa268b393ad5414e4db097ce9492d, 0x041f685536314ddd11441a3d7e01157f7ea7e474aae449dbba70c2edc70cd573); 79 | if (i == 45) return Utils.G1Point(0x191365dba9df566e0e6403fb9bcd6847c0964ea516c403fd88543a6a9b3fa1f2, 0x0ae815170115c7ce78323cbd9399735847552b379c2651af6fc29184e95eef7f); 80 | if (i == 46) return Utils.G1Point(0x027a2a874ba2ab278be899fe96528b6d39f9d090ef4511e68a3e4979bc18a526, 0x2272820981fe8a9f0f7c4910dd601cea6dd7045aa4d91843d3cf2afa959fbe68); 81 | if (i == 47) return Utils.G1Point(0x13feec071e0834433193b7be17ce48dec58d7610865d9876a08f91ea79c7e28d, 0x26325544133c7ec915c317ac358273eb2bf2e6b6119922d7f0ab0727e5eb9e64); 82 | if (i == 48) return Utils.G1Point(0x08e6096c8425c13b79e6fa38dffcc92c930d1d0bff9671303dbc0445e73c77bc, 0x03e884c8dc85f0d80baf968ae0516c1a7927808f83b4615665c67c59389db606); 83 | if (i == 49) return Utils.G1Point(0x1217ff3c630396cd92aa13aa6fee99880afc00f47162625274090278f09cbed3, 0x270b44f96accb061e9cad4a3341d72986677ed56157f3ba02520fdf484bb740d); 84 | if (i == 50) return Utils.G1Point(0x239128d2e007217328aae4e510c3d9fe1a3ef2b23212dfaf6f2dcb75ef08ed04, 0x2d5495372c759fdba858b7f6fa89a948eb4fd277bae9aebf9785c86ea3f9c07d); 85 | if (i == 51) return Utils.G1Point(0x305747313ea4d7d17bd14b69527094fa79bdc05c3cc837a668a97eb81cffd3d4, 0x0aa43bd7ad9090012e12f78ac3cb416903c2e1aabb61161ca261892465b3555d); 86 | if (i == 52) return Utils.G1Point(0x267742bd96caad20a76073d5060085103b7d29c88f0a0d842ef610472a1764ef, 0x0086485faeedd1ea8f6595b2edaf5f99044864271a178bd33e6d5b73b6d240a0); 87 | if (i == 53) return Utils.G1Point(0x00aed2e1ac448b854a44c7aa43cabb93d92316460c8f5eacb038f4cf554dfa01, 0x1b2ec095d370b234214a0c68fdfe8da1e06cbfdc5e889e2337ccb28c49089fcf); 88 | if (i == 54) return Utils.G1Point(0x06f37ac505236b2ed8c520ea36b0448229eb2f2536465b14e6e115dc810c6e39, 0x174db60e92b421e4d59c81e2c0666f7081067255c8e0d775e085278f34663490); 89 | if (i == 55) return Utils.G1Point(0x2af094e58a7961c4a1dba0685d8b01dacbb01f0fc0e7a648085a38aa380a7ab6, 0x108ade796501042dab10a83d878cf1deccf74e05edc92460b056d31f3e39fd53); 90 | if (i == 56) return Utils.G1Point(0x051ec23f1166a446caa4c8ff443470e98e753697fcceb4fbe5a49bf7a2db7199, 0x00f938707bf367e519d0c5efcdb61cc5a606901c0fbd4565abeeb5d020081d96); 91 | if (i == 57) return Utils.G1Point(0x1132459cf7287884b102467a71fad0992f1486178f7385ef159277b6e800239d, 0x257fedb1e126363af3fb3a80a4ad850d43041d64ef27cc5947730901f3019138); 92 | if (i == 58) return Utils.G1Point(0x14a571bbbb8d2a442855cde5fe6ed635d91668eded003d7698f9f744557887ea, 0x0f65f76e6fa6f6c7f765f947d905b015c3ad077219fc715c2ec40e37607c1041); 93 | if (i == 59) return Utils.G1Point(0x0e303c28b0649b95c624d01327a61fd144d29bfed6d3a1cf83216b45b78180cf, 0x229975c2e3aaba1d6203a5d94ea92605edb2af04f41e3783ec4e64755eeb1d1b); 94 | if (i == 60) return Utils.G1Point(0x05a62a2f1dfe368e81d9ae5fe150b9a57e0f85572194de27f48fec1c5f3b0dad, 0x200eb8097c91fe825adb0e3920e6bdff2e40114bd388298b85a0094a9a5bc654); 95 | if (i == 61) return Utils.G1Point(0x06545efc18dfc2f444e147c77ed572decd2b58d0668bbaaf0d31f1297cde6b99, 0x29ecbbeb81fe6c14279e9e46637ad286ba71e4c4e5da1416d8501e691f9e5bed); 96 | if (i == 62) return Utils.G1Point(0x045ce430f0713c29748e30d024cd703a5672633faebe1fd4d210b5af56a50e70, 0x0e3ec93722610f4599ffaac0db0c1b2bb446ff5aea5117710c271d1e64348844); 97 | if (i == 63) return Utils.G1Point(0x243de1ee802dd7a3ca9a991ec228fbbfb4973260f905b5106e5f738183d5cacd, 0x133d25bb8dc9f54932b9d6ee98e0432676f5278e9878967fbbd8f5dfc46df4f8); 98 | } 99 | 100 | function hs(uint256 i) public pure returns (Utils.G1Point memory) { 101 | if (i == 0) return Utils.G1Point(0x01d39aef1308fae84642befcdb6c07f655cc4d092f6a66f464cb9c959bff743a, 0x277420423ebed18174bd2730d4387b06c10958e564af6444333ac5b30767c59c); 102 | if (i == 1) return Utils.G1Point(0x2f1a6e72cf51c976df65f69457491bd852b4cf8a172183537dc413d0801bef0a, 0x0fc8845b156f86c3018d7a193c089c8d02ea38ba2cec11b1b6118a3b37f4cb08); 103 | if (i == 2) return Utils.G1Point(0x00f698cd9c34ea5fc62bd7d91c3a8b7f70bb12596d3c6d99b9be4d7acf2e72ea, 0x23abea6d9096d3c23f3aee1447570211efc5d2add2f310a2acaf3afc1faa0ed1); 104 | if (i == 3) return Utils.G1Point(0x06e93364d8080a84ab1dac7fa743b3f3f139f84c602cc67a899e3739abf11cc0, 0x2246590e06850a6f55b3e9bb81d7316fe7b08bef9f9a06d43b30226d626a979d); 105 | if (i == 4) return Utils.G1Point(0x1fb8f0bbb173c6d8f7ae2e1fa1e3770aa8c66fbed8d459d8e6fa972c990e0e22, 0x23d30ccd0b4747679bbd29620c3efb39ee1d7018b0281c448ad1501a5e04dc1a); 106 | if (i == 5) return Utils.G1Point(0x1b5f7c9fa9f3ef4adbed1f09bc6e151ba5e7c1d098c2d94e2dbe95897e6675cd, 0x23ff89ca0d326bd98629bf7ccf343ababdb330821a495b7624d8720fd1ead1e3); 107 | if (i == 6) return Utils.G1Point(0x2ffd2415cb4cd71a9f3cf4ed64d4a85d4d3eb06bfa10f98cb8a2ab7e2d96797c, 0x1d770c3d19238753457dd36280bd6685f6f214461a81aa95962f1c80a6c4168d); 108 | if (i == 7) return Utils.G1Point(0x2d344a9de673000e4108f8b6eb21b8cf39e223fad81cef47cd599b5e548a092b, 0x1abe37b046f84fa46b7629e432e298ae7dda657d2cdde851775431cab1d34402); 109 | if (i == 8) return Utils.G1Point(0x131bea29a212d81278492c44179c04f2a3f7e72151a0a4870b01e2fa96cdf84a, 0x0e5a783a7d6e044761fa10b801de33a1c4de8d4569f132b86a5be6aa13726127); 110 | if (i == 9) return Utils.G1Point(0x2e9de6196c9d4be4d765078245515d02b18ee6073ca0afb1afe98dcca2378d76, 0x1a5be81d26e9261e5072bb86f5cbd1dd8075316c8fec769ac839819a17ec3841); 111 | if (i == 10) return Utils.G1Point(0x21ccb04d241aa8108e9e5f2487fffe82debc69e4cff3a7ee292609fbe49cb6ad, 0x14d2e86d8bea6af2ad1cde303c9b2993a37c5b7bf0567278854ca666e61f2e80); 112 | if (i == 11) return Utils.G1Point(0x164314a3b09437cc1cd0f7726b8291be0bd293876093e51f989feab3238cfd85, 0x043bb4c392fbf35b9991d01ffaf6c59d7e72559ed7f338f85beebdf74ed3132f); 113 | if (i == 12) return Utils.G1Point(0x08a85c13ee191db8c043a21db38c016e27376d82063a93f8a6ff603b0f396433, 0x19be7f870a4bbd255c61ca01588bc3be2632c015753a3320309915e600d78a0a); 114 | if (i == 13) return Utils.G1Point(0x2090c3ab526ff54497f984b860682c77c0a89842f6612928cf4188c5c0f1ee20, 0x151a9c9fcdc438b3197d85ab51317d969d66e03fe26e05f6be466058cb8b7e65); 115 | if (i == 14) return Utils.G1Point(0x220b0c31ba1c84a2c1235d987e79d8fb1854fb59cce44719a13e4b83331da63b, 0x19a161498b4d63a027670174b424260b2180ccb02e05e4e061363ac3a87642da); 116 | if (i == 15) return Utils.G1Point(0x018eb881dd184f8abff3b91b50676a12945e205f200fdaf25ffb7e8c97385334, 0x1dea48b102351f75ce4977a6c3c908455a9e269aab69c3f66e642791052d0cfb); 117 | if (i == 16) return Utils.G1Point(0x07b0183a2450ccb5a001554ac3fe1a763bb69a0222316c1a553124a915cd0720, 0x282216c8c2711780ed3b24281fdd358d0e3d2e05e9cd1ab6842432f818a4a40c); 118 | if (i == 17) return Utils.G1Point(0x2b3f257e1258a3c2bda28be60fdc4cf2a74a19bb17d61783a91ec478d379e1a5, 0x1a8ddf17a83d7b89a6c7ae59601b736c4c7022f29c74700bd5d51cbd70b5051d); 119 | if (i == 18) return Utils.G1Point(0x0485fd181e30eef43c4356c6cdfb8957267795c838e6e64c52fd81a697dd8505, 0x17105695b4bfc555a55c8449182a6335584f971a0058172bd2b5441db3129843); 120 | if (i == 19) return Utils.G1Point(0x2008a80d7c60d7dc6e069b174efd31984a0933da7f89a574aae52e8805b40095, 0x052398552fb4706758b6eafb50bed493568670961058586735bca016e875e6ef); 121 | if (i == 20) return Utils.G1Point(0x119ff93e1bce3d5c7c57d1fea845e9335e04c729ec7a62ca2283d6c5dc0acc7c, 0x2042b68991a4d4c959df76947ef2594afb6735d760c3629825db8451b4830a3c); 122 | if (i == 21) return Utils.G1Point(0x0ed374dfa5daee92868812764c47ffd9c0c832abe09124f6f55283869d639eb7, 0x267767cb5017979990d9fa6db5f741de043afb70ee8a5e29045e926486f00858); 123 | if (i == 22) return Utils.G1Point(0x1c3786f37ee4f7eb9493551cea3c2a4e8ddcdd3c86e9f9ea2a41199efa1da476, 0x147d40e13345ec2f38975b09989d2c01954122796f83bfc19974ab647f754a32); 124 | if (i == 23) return Utils.G1Point(0x0040bf79ad3c473ffd4d7e15dbe0fa0a9b06e765a6d5adb372f98b8ea107f2c6, 0x17bf761b14f52da007532fcdf1bbdec180750af1b7b3804e29d6d45af62042f8); 125 | if (i == 24) return Utils.G1Point(0x01a9c26d59a9962250ce2b20b477884d11ce2c2404b749ceee59c51c2dcc0918, 0x1603d5448eb9b7528b247c0cdf8b0d9275322975bc7e4b13b8d0312cf032c467); 126 | if (i == 25) return Utils.G1Point(0x215ecf3e09641d5a38d4f510ed72e2ee586d4fbfc7e46411e1a3396f07b1e276, 0x28ece25edfb8c48631b861e838641f8e61e58afcf4e6c8f336c86fe5b7c0dfc9); 127 | if (i == 26) return Utils.G1Point(0x0beda6c3cbaec7226ed3bd6e0a27a626e0022b1afa820ac509e21b646f23dc60, 0x212f09e343da69ec34d90491282e69499c779973c0352126a38aabbf5783b288); 128 | if (i == 27) return Utils.G1Point(0x27f5c2199a6cebc34e3b5376b4db3ac6db08d2f302aa9b99f808e20a95e9ef8c, 0x0ccc4c0723e2a255e9b649eae9c16d72f4ddb97d088d7b3154c00e9a1dd94fe8); 129 | if (i == 28) return Utils.G1Point(0x2af5191d45c6ca76563c6f936f0cd2dcaa4311719675c2bb5f65d3df2270f636, 0x1252aca114b1fda7f43c06d1f2b60718e7bc99b8544138f9c67aad8dfca863d7); 130 | if (i == 29) return Utils.G1Point(0x13bdce5de7cf1c2250bac0be0d23d3be0140ce3838c8966ea2870e64b87adaee, 0x2f3770a6b5a9babcc5fa7cae8ffbb2a63ff312f2d3352e4fe8c173b12ff847e0); 131 | if (i == 30) return Utils.G1Point(0x18d1242b7bee604de29b4511814b02c8fd1519a4fc6daf9dbc95f8bb64ee097b, 0x0f828debef5bd4115c91f419718bdb59464bd8bb78fd0dc250d1efb1a51366df); 132 | if (i == 31) return Utils.G1Point(0x04b4102e8d3a2d3ba330257de8d18861db5652d685efb297d9c116eb1a7b1299, 0x08a3fd325f19ddebb53063d60fccdb8f0321fe41d4d93d98c65e05c9b4101aa0); 133 | if (i == 32) return Utils.G1Point(0x20f38c332b7117550a2462637fd38dfa08eb063e5bbc1838de2d8a933b052a5d, 0x0de3339a34e84bc8d57daf4fe55855a02df1c6fe4ce1cd07ca3060f67e1d75b2); 134 | if (i == 33) return Utils.G1Point(0x02f501714aa467e8b06ec808af8a3278f58faa7b87b678a1e36ee779adb01def, 0x1b8f1369d47a1d7b4da91b777bbcd7a2a4bde8ad09cc2eeeb9e8c0036ef5df47); 135 | if (i == 34) return Utils.G1Point(0x059c89b0e337c65e8132ac7c78f29d1a016edbff65da6663ef114f85bc414f20, 0x0b6e3d301ca62d0946299c6b79f2207479351ac27478901cdf5be144cf77435f); 136 | if (i == 35) return Utils.G1Point(0x02f51c34b66cd01304c185bcc087b9430beb0e6738e97491550740e18c262948, 0x27e42ced0bf3356a10e9685f1365a2ac3fdb3f3e89b9cd2f0309cd9ffcd6dfc0); 137 | if (i == 36) return Utils.G1Point(0x28c0affe0178e407e8196e3d0af3674aecc46a94342a97fec96d1eaa0e24ce3a, 0x1056737f11d45d9de7ff2d6de4ae31af9aa6a3ca2a0d56e5748059c7c39a02e7); 138 | if (i == 37) return Utils.G1Point(0x0100b2eb3ec56d3c557be418c4aabf0229ba4fb58c0bbb0756802e9f1573e245, 0x10a6e05da67b0cab1b2ded1f6e29f2c55279c738e18bbb91687fb046bac7789c); 139 | if (i == 38) return Utils.G1Point(0x0fe1fdb40a1c4b49772635241e37196fdca6a3cbd8ac2c550e1a48c90ec30029, 0x064ac2c20c146923131bab9ff316498a29fdce765a06c4a891f5b36993f52dba); 140 | if (i == 39) return Utils.G1Point(0x0c0aadc1d96e9b0b609e9f455c85ecf9506bbb7972f4adf58a3731f40cfd5d77, 0x1f3941c16c4c9da3c169c71abb9557d8b7b54d4b0998410d91d1b4a759f15028); 141 | if (i == 40) return Utils.G1Point(0x0a46308afef5a8af8f3b822aaa413d2961845a361f05cab5524144e74699cdec, 0x1035f4f2bf0b1ae6d0524d1309829c6d997cd7010650ca05a1bf585206e1aa3b); 142 | if (i == 41) return Utils.G1Point(0x1ccf854703b8608e10416032eaeadcc7ef236f2d1d33fec289d6db28db10b517, 0x1dbd7e3ed44a0fc339078bcb420b2641210a930a95eecc2aec0147a1abcbbb1a); 143 | if (i == 42) return Utils.G1Point(0x1408a19ef2793b8af811e95ffbdf901671a3b76bdc2203be5fde5475de4c54bc, 0x26431b0fbb7fb432a0edc0b247fee08d8f44a2abb0cb9b4b8a8a040bdea3cbf8); 144 | if (i == 43) return Utils.G1Point(0x2eb3aa4eb2234e4de8d30bcfeca595e758bc542da4ee111722fd6be47defd7e8, 0x1a7d7ab203974731e8f33dbbc7af481bbb64e47407e998d2d26dfa90a9dc321b); 145 | if (i == 44) return Utils.G1Point(0x1b6c0f4b954626f03f4fe59bc83ecc9ac2279d7d20746829583b66735cbb4830, 0x2eb200acc2138afec4e5f53438273760ca4d46bd0ebfa0155ae62a8055fee316); 146 | if (i == 45) return Utils.G1Point(0x0241820580d821b485c5d3f905cfc4a407881bbc7e041b4e50e2f628f88afc49, 0x2ee28fcaecd349babc91cb6fc9d65ed51dac6e2dd118898e3a0ee1bf0e94793d); 147 | if (i == 46) return Utils.G1Point(0x0b7b54391ce78ebf1aa3b4b2a75958f1702100aef8163810f89d0ad81c04ed78, 0x129075ea4b1ab58683019ab79340b2b090b9720721046332d8e0e80b2039406e); 148 | if (i == 47) return Utils.G1Point(0x18c8880c588c4dd3d657439a3357ff3bf0f44b9074d5d7aebb384fbac7e58090, 0x305de2ed95fe36ca48642098d98180b4ab92a03978fa6a038d80e546da989e6a); 149 | if (i == 48) return Utils.G1Point(0x00f185128b4341f79c914ef9739c830294df8da311891416babcc53e364ef245, 0x0a1ee67a755420fe0835770271142c883ebe3721140075a1677f2d57c6cec4b3); 150 | if (i == 49) return Utils.G1Point(0x2cf787f4957c6af6a6431d4a1577df0c71b6b44cca9771d8dee49ed83b024008, 0x25dfce7a0c6515b610f0b602d4083adfa436cbf1cce0e3dbec14338bee6ef501); 151 | if (i == 50) return Utils.G1Point(0x19934b0990d3b31864dcd3a9a7fe8ea20c87ef0abc3980c81035234b961b6c20, 0x2b8ca35cc74606b825937545131cb3c9248ec880b8df7c5eeac6d2be85aff646); 152 | if (i == 51) return Utils.G1Point(0x2adbdb8197cd82851b706df9c38a53950b1ba5953c8e7fcf3a037e4af817f706, 0x0cd2df6ffbde434614d0288d75ef6afd5d8f0c1b831d38b7de57785658b4bfe9); 153 | if (i == 52) return Utils.G1Point(0x1ee70de811fe6abb48823d75549e97bb81e3e98aea57e03b03164601b45a8889, 0x18ff1b711d742b30520fb8aeb174940d0e78ad926e0747cd3cf6cd9fdac1eb83); 154 | if (i == 53) return Utils.G1Point(0x2d831e2ba4c03354502c9ec8569eb4f1b7617b92e90e6bd2df617273793af02e, 0x1d838e04c75622032862a0ad64e997f99b64f9dce9dfd71b25214dc75371ef53); 155 | if (i == 54) return Utils.G1Point(0x0816128c1a69aacf266b28efd029bd12998f9abbfaa42c6b175d13452e81ec74, 0x084f00999de16016819beea6c19bade38d1802ac9ea2a59c70a94ab43676423f); 156 | if (i == 55) return Utils.G1Point(0x19fbf07d90fb1fc051cf76bc3ca6fb551463834456cac5a40a7e50dc492b6e07, 0x136cccfcd75ba252a946fc7e8d323ed9afdba4990600f97c8ea69ed72759c756); 157 | if (i == 56) return Utils.G1Point(0x2c0dca3a80d643d69ac2ccff2c16e727aa5eb81839a0b46e9b9f351941100e86, 0x0d90cee7e881d7484d76b29524af629358dc9795a2a789606fdec6d73e161435); 158 | if (i == 57) return Utils.G1Point(0x134b5d77b0c39945e9c8a7701bf5058183c5dc2010ab6ab6061243b2d748c4fa, 0x0d6297624431107091b2ccfc7c4f6964a14521ebecc4ca4687ad11ac439c9bc1); 159 | if (i == 58) return Utils.G1Point(0x1eff41015f3733fb8a295ff8a513d992d8723a159a294b5c444919ba22beb549, 0x0006941da956684261258a79a72fcf1b10e23e3f5844f808749fe10818cade97); 160 | if (i == 59) return Utils.G1Point(0x05d6227f2a9650a4b35412a9369f96155487d28e0f1827bce5fe2748e2b39c4f, 0x1640729260ba5f06592f23e8d2cf9b0a40ba5d090539b3d3f03e9a9bf8f6aad3); 161 | if (i == 60) return Utils.G1Point(0x166793ff28c5d31cf3c50fe736340af6cc6d6c80749bbcfd66db78ed80408e50, 0x2015c5c83fb2bb673aeb63e79928fa4c3a8ac6eb758b643e6bb9ff416ec6f3a5); 162 | if (i == 61) return Utils.G1Point(0x09ea2a4226678267f88c933e6f947fa16648a7710d169e715048e336d1b4129d, 0x26bb40f1b5f88a0a63acebd040aba0bbf85b03e04760bf5be723bd42d0f7d0ae); 163 | if (i == 62) return Utils.G1Point(0x0fe50825f829d35375a488cff7df34638241bce1a5b2f48c39635651e24c470d, 0x049b06661bb12c19ba643933a06d93035ecec6f53c61b8d4d2b39cc5c0459e68); 164 | if (i == 63) return Utils.G1Point(0x0b8871057f2a8bf0f794c099fba2481b9f39457d55d7e472e5dc994d69f0fbb8, 0x072c9e81fc2e118414a9fb6d9fff6e5b615f07fa980e3ce692a09bce95cc54f2); 165 | } 166 | 167 | struct IPAuxiliaries { 168 | uint256 o; 169 | uint256[] challenges; 170 | uint256[] s; 171 | } 172 | 173 | function verify(InnerProductStatement memory statement, InnerProductProof memory proof, uint256 salt) internal view returns (bool) { 174 | uint256 log_n = proof.L.length; 175 | uint256 n = 1 << log_n; 176 | 177 | IPAuxiliaries memory ipAuxiliaries; // for stacktoodeep 178 | ipAuxiliaries.o = salt; // could probably just access / overwrite the parameter directly. 179 | ipAuxiliaries.challenges = new uint256[](log_n); 180 | for (uint256 i = 0; i < log_n; i++) { 181 | ipAuxiliaries.o = uint256(keccak256(abi.encode(ipAuxiliaries.o, proof.L[i], proof.R[i]))).mod(); // overwrites 182 | ipAuxiliaries.challenges[i] = ipAuxiliaries.o; 183 | uint256 inverse = ipAuxiliaries.o.inv(); 184 | statement.P = statement.P.add(proof.L[i].mul(ipAuxiliaries.o.mul(ipAuxiliaries.o)).add(proof.R[i].mul(inverse.mul(inverse)))); 185 | } 186 | 187 | ipAuxiliaries.s = new uint256[](n); 188 | ipAuxiliaries.s[0] = 1; 189 | // credit to https://github.com/leanderdulac/BulletProofLib/blob/master/truffle/contracts/EfficientInnerProductVerifier.sol for the below block. 190 | // it is an unusual and clever variant of what we already do in ZetherVerifier.sol:234-249, but with the special property that it requires only 1 inversion. 191 | // indeed, that algorithm computes the same function as this, yet its use here would require log(N) modular inversions. inversions turn out to be expensive. 192 | // of course in that case don't have to invert, but rather to do x minus, etc., so in that case we might as well just use the simpler algorithm. 193 | for (uint256 i = 0; i < log_n; i++) ipAuxiliaries.s[0] = ipAuxiliaries.s[0].mul(ipAuxiliaries.challenges[i]); 194 | ipAuxiliaries.s[0] = ipAuxiliaries.s[0].inv(); // here. 195 | bool[] memory bitSet = new bool[](n); 196 | for (uint256 i = 0; i < n / 2; i++) { 197 | for (uint256 j = 0; (1 << j) + i < n; j++) { 198 | uint256 k = i + (1 << j); 199 | if (!bitSet[k]) { 200 | ipAuxiliaries.s[k] = ipAuxiliaries.s[i].mul(ipAuxiliaries.challenges[log_n - 1 - j]).mul(ipAuxiliaries.challenges[log_n - 1 - j]); 201 | bitSet[k] = true; 202 | } 203 | } 204 | } 205 | 206 | Utils.G1Point memory temp = statement.u.mul(proof.a.mul(proof.b)); 207 | for (uint256 i = 0; i < n; i++) { 208 | temp = temp.add(gs(i).mul(ipAuxiliaries.s[i].mul(proof.a))); 209 | temp = temp.add(statement.hs[i].mul(ipAuxiliaries.s[n - 1 - i].mul(proof.b))); 210 | } 211 | require(temp.eq(statement.P), "Inner product equality check failure."); 212 | 213 | return true; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /packages/protocol/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License 2.0 2 | pragma solidity ^0.7.0; 3 | 4 | contract Migrations { 5 | address public owner; 6 | 7 | // A function with the signature `last_completed_migration()`, returning a uint, is required. 8 | uint public last_completed_migration; 9 | 10 | modifier restricted() { 11 | if (msg.sender == owner) _; 12 | } 13 | 14 | constructor() { 15 | owner = msg.sender; 16 | } 17 | 18 | // A function with the signature `setCompleted(uint)` is required. 19 | function setCompleted(uint completed) public restricted { 20 | last_completed_migration = completed; 21 | } 22 | 23 | function upgrade(address new_address) public restricted { 24 | Migrations upgraded = Migrations(new_address); 25 | upgraded.setCompleted(last_completed_migration); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/protocol/contracts/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License 2.0 2 | pragma solidity ^0.7.0; 3 | 4 | library Utils { 5 | 6 | uint256 constant GROUP_ORDER = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; 7 | uint256 constant FIELD_ORDER = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; 8 | 9 | function add(uint256 x, uint256 y) internal pure returns (uint256) { 10 | return addmod(x, y, GROUP_ORDER); 11 | } 12 | 13 | function mul(uint256 x, uint256 y) internal pure returns (uint256) { 14 | return mulmod(x, y, GROUP_ORDER); 15 | } 16 | 17 | function inv(uint256 x) internal view returns (uint256) { 18 | return exp(x, GROUP_ORDER - 2); 19 | } 20 | 21 | function mod(uint256 x) internal pure returns (uint256) { 22 | return x % GROUP_ORDER; 23 | } 24 | 25 | function sub(uint256 x, uint256 y) internal pure returns (uint256) { 26 | return x >= y ? x - y : GROUP_ORDER - y + x; 27 | } 28 | 29 | function neg(uint256 x) internal pure returns (uint256) { 30 | return GROUP_ORDER - x; 31 | } 32 | 33 | function exp(uint256 base, uint256 exponent) internal view returns (uint256 output) { 34 | uint256 order = GROUP_ORDER; 35 | assembly { 36 | let m := mload(0x40) 37 | mstore(m, 0x20) 38 | mstore(add(m, 0x20), 0x20) 39 | mstore(add(m, 0x40), 0x20) 40 | mstore(add(m, 0x60), base) 41 | mstore(add(m, 0x80), exponent) 42 | mstore(add(m, 0xa0), order) 43 | if iszero(staticcall(gas(), 0x05, m, 0xc0, m, 0x20)) { // staticcall or call? 44 | revert(0, 0) 45 | } 46 | output := mload(m) 47 | } 48 | } 49 | 50 | function fieldExp(uint256 base, uint256 exponent) internal view returns (uint256 output) { // warning: mod p, not q 51 | uint256 order = FIELD_ORDER; 52 | assembly { 53 | let m := mload(0x40) 54 | mstore(m, 0x20) 55 | mstore(add(m, 0x20), 0x20) 56 | mstore(add(m, 0x40), 0x20) 57 | mstore(add(m, 0x60), base) 58 | mstore(add(m, 0x80), exponent) 59 | mstore(add(m, 0xa0), order) 60 | if iszero(staticcall(gas(), 0x05, m, 0xc0, m, 0x20)) { // staticcall or call? 61 | revert(0, 0) 62 | } 63 | output := mload(m) 64 | } 65 | } 66 | 67 | struct G1Point { 68 | bytes32 x; 69 | bytes32 y; 70 | } 71 | 72 | function add(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) { 73 | assembly { 74 | let m := mload(0x40) 75 | mstore(m, mload(p1)) 76 | mstore(add(m, 0x20), mload(add(p1, 0x20))) 77 | mstore(add(m, 0x40), mload(p2)) 78 | mstore(add(m, 0x60), mload(add(p2, 0x20))) 79 | if iszero(staticcall(gas(), 0x06, m, 0x80, r, 0x40)) { 80 | revert(0, 0) 81 | } 82 | } 83 | } 84 | 85 | function mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { 86 | assembly { 87 | let m := mload(0x40) 88 | mstore(m, mload(p)) 89 | mstore(add(m, 0x20), mload(add(p, 0x20))) 90 | mstore(add(m, 0x40), s) 91 | if iszero(staticcall(gas(), 0x07, m, 0x60, r, 0x40)) { 92 | revert(0, 0) 93 | } 94 | } 95 | } 96 | 97 | function neg(G1Point memory p) internal pure returns (G1Point memory) { 98 | return G1Point(p.x, bytes32(FIELD_ORDER - uint256(p.y))); // p.y should already be reduced mod P? 99 | } 100 | 101 | function eq(G1Point memory p1, G1Point memory p2) internal pure returns (bool) { 102 | return p1.x == p2.x && p1.y == p2.y; 103 | } 104 | 105 | function g() internal pure returns (G1Point memory) { 106 | return G1Point(0x077da99d806abd13c9f15ece5398525119d11e11e9836b2ee7d23f6159ad87d4, 0x01485efa927f2ad41bff567eec88f32fb0a0f706588b4e41a8d587d008b7f875); 107 | } 108 | 109 | function h() internal pure returns (G1Point memory) { 110 | return G1Point(0x01b7de3dcf359928dd19f643d54dc487478b68a5b2634f9f1903c9fb78331aef, 0x2bda7d3ae6a557c716477c108be0d0f94abc6c4dc6b1bd93caccbcceaaa71d6b); 111 | } 112 | 113 | function mapInto(uint256 seed) internal view returns (G1Point memory) { 114 | uint256 y; 115 | while (true) { 116 | uint256 ySquared = fieldExp(seed, 3) + 3; // addmod instead of add: waste of gas, plus function overhead cost 117 | y = fieldExp(ySquared, (FIELD_ORDER + 1) / 4); 118 | if (fieldExp(y, 2) == ySquared) { 119 | break; 120 | } 121 | seed += 1; 122 | } 123 | return G1Point(bytes32(seed), bytes32(y)); 124 | } 125 | 126 | function mapInto(string memory input) internal view returns (G1Point memory) { 127 | return mapInto(uint256(keccak256(abi.encodePacked(input))) % FIELD_ORDER); 128 | } 129 | 130 | function mapInto(string memory input, uint256 i) internal view returns (G1Point memory) { 131 | return mapInto(uint256(keccak256(abi.encodePacked(input, i))) % FIELD_ORDER); 132 | } 133 | 134 | function slice(bytes memory input, uint256 start) internal pure returns (bytes32 result) { 135 | assembly { 136 | let m := mload(0x40) 137 | mstore(m, mload(add(add(input, 0x20), start))) // why only 0x20? 138 | result := mload(m) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /packages/protocol/contracts/ZSC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License 2.0 2 | pragma solidity ^0.7.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./CashToken.sol"; 6 | import "./Utils.sol"; 7 | import "./InnerProductVerifier.sol"; 8 | import "./ZetherVerifier.sol"; 9 | import "./BurnVerifier.sol"; 10 | 11 | contract ZSC { 12 | using Utils for uint256; 13 | using Utils for Utils.G1Point; 14 | 15 | CashToken coin; 16 | ZetherVerifier zetherVerifier; 17 | BurnVerifier burnVerifier; 18 | uint256 public epochLength; 19 | uint256 public fee; 20 | 21 | uint256 constant MAX = 4294967295; // 2^32 - 1 // no sload for constants...! 22 | mapping(bytes32 => Utils.G1Point[2]) acc; // main account mapping 23 | mapping(bytes32 => Utils.G1Point[2]) pending; // storage for pending transfers 24 | mapping(bytes32 => uint256) lastRollOver; 25 | bytes32[] nonceSet; // would be more natural to use a mapping, but they can't be deleted / reset! 26 | uint256 lastGlobalUpdate = 0; // will be also used as a proxy for "current epoch", seeing as rollovers will be anticipated 27 | // not implementing account locking for now...revisit 28 | 29 | event TransferOccurred(Utils.G1Point[] parties, Utils.G1Point beneficiary); 30 | // arg is still necessary for transfers---not even so much to know when you received a transfer, as to know when you got rolled over. 31 | 32 | constructor(address _coin, address _zether, address _burn, uint256 _epochLength) { // visibiility won't be needed in 7.0 33 | // epoch length, like block.time, is in _seconds_. 4 is the minimum!!! (To allow a withdrawal to go through.) 34 | coin = CashToken(_coin); 35 | zetherVerifier = ZetherVerifier(_zether); 36 | burnVerifier = BurnVerifier(_burn); 37 | epochLength = _epochLength; 38 | fee = zetherVerifier.fee(); 39 | Utils.G1Point memory empty; 40 | pending[keccak256(abi.encode(empty))][1] = Utils.g(); // "register" the empty account... 41 | } 42 | 43 | function simulateAccounts(Utils.G1Point[] memory y, uint256 epoch) view public returns (Utils.G1Point[2][] memory accounts) { 44 | // in this function and others, i have to use public + memory (and hence, a superfluous copy from calldata) 45 | // only because calldata structs aren't yet supported by solidity. revisit this in the future. 46 | uint256 size = y.length; 47 | accounts = new Utils.G1Point[2][](size); 48 | for (uint256 i = 0; i < size; i++) { 49 | bytes32 yHash = keccak256(abi.encode(y[i])); 50 | accounts[i] = acc[yHash]; 51 | if (lastRollOver[yHash] < epoch) { 52 | Utils.G1Point[2] memory scratch = pending[yHash]; 53 | accounts[i][0] = accounts[i][0].add(scratch[0]); 54 | accounts[i][1] = accounts[i][1].add(scratch[1]); 55 | } 56 | } 57 | } 58 | 59 | function rollOver(bytes32 yHash) internal { 60 | uint256 e = block.timestamp / epochLength; 61 | if (lastRollOver[yHash] < e) { 62 | Utils.G1Point[2][2] memory scratch = [acc[yHash], pending[yHash]]; 63 | acc[yHash][0] = scratch[0][0].add(scratch[1][0]); 64 | acc[yHash][1] = scratch[0][1].add(scratch[1][1]); 65 | // acc[yHash] = scratch[0]; // can't do this---have to do the above instead (and spend 2 sloads / stores)---because "not supported". revisit 66 | delete pending[yHash]; // pending[yHash] = [Utils.G1Point(0, 0), Utils.G1Point(0, 0)]; 67 | lastRollOver[yHash] = e; 68 | } 69 | if (lastGlobalUpdate < e) { 70 | lastGlobalUpdate = e; 71 | delete nonceSet; 72 | } 73 | } 74 | 75 | function registered(bytes32 yHash) internal view returns (bool) { 76 | Utils.G1Point memory zero = Utils.G1Point(0, 0); 77 | Utils.G1Point[2][2] memory scratch = [acc[yHash], pending[yHash]]; 78 | return !(scratch[0][0].eq(zero) && scratch[0][1].eq(zero) && scratch[1][0].eq(zero) && scratch[1][1].eq(zero)); 79 | } 80 | 81 | function register(Utils.G1Point memory y, uint256 c, uint256 s) public { 82 | // allows y to participate. c, s should be a Schnorr signature on "this" 83 | Utils.G1Point memory K = Utils.g().mul(s).add(y.mul(c.neg())); 84 | uint256 challenge = uint256(keccak256(abi.encode(address(this), y, K))).mod(); 85 | require(challenge == c, "Invalid registration signature!"); 86 | bytes32 yHash = keccak256(abi.encode(y)); 87 | require(!registered(yHash), "Account already registered!"); 88 | // pending[yHash] = [y, Utils.g()]; // "not supported" yet, have to do the below 89 | pending[yHash][0] = y; 90 | pending[yHash][1] = Utils.g(); 91 | } 92 | 93 | function fund(Utils.G1Point memory y, uint256 bTransfer) public { 94 | bytes32 yHash = keccak256(abi.encode(y)); 95 | require(registered(yHash), "Account not yet registered."); 96 | rollOver(yHash); 97 | 98 | require(bTransfer <= MAX, "Deposit amount out of range."); // uint, so other way not necessary? 99 | 100 | Utils.G1Point memory scratch = pending[yHash][0]; 101 | scratch = scratch.add(Utils.g().mul(bTransfer)); 102 | pending[yHash][0] = scratch; 103 | require(coin.transferFrom(msg.sender, address(this), bTransfer), "Transfer from sender failed."); 104 | require(coin.balanceOf(address(this)) <= MAX, "Fund pushes contract past maximum value."); 105 | } 106 | 107 | function transfer(Utils.G1Point[] memory C, Utils.G1Point memory D, Utils.G1Point[] memory y, Utils.G1Point memory u, bytes memory proof, Utils.G1Point memory beneficiary) public { 108 | uint256 size = y.length; 109 | Utils.G1Point[] memory CLn = new Utils.G1Point[](size); 110 | Utils.G1Point[] memory CRn = new Utils.G1Point[](size); 111 | require(C.length == size, "Input array length mismatch!"); 112 | 113 | bytes32 beneficiaryHash = keccak256(abi.encode(beneficiary)); 114 | require(registered(beneficiaryHash), "Miner's account is not yet registered."); // necessary so that receiving a fee can't "backdoor" you into registration. 115 | rollOver(beneficiaryHash); 116 | pending[beneficiaryHash][0] = pending[beneficiaryHash][0].add(Utils.g().mul(fee)); 117 | 118 | for (uint256 i = 0; i < size; i++) { 119 | bytes32 yHash = keccak256(abi.encode(y[i])); 120 | require(registered(yHash), "Account not yet registered."); 121 | rollOver(yHash); 122 | Utils.G1Point[2] memory scratch = pending[yHash]; 123 | pending[yHash][0] = scratch[0].add(C[i]); 124 | pending[yHash][1] = scratch[1].add(D); 125 | // pending[yHash] = scratch; // can't do this, so have to use 2 sstores _anyway_ (as in above) 126 | 127 | scratch = acc[yHash]; // trying to save an sload, i guess. 128 | CLn[i] = scratch[0].add(C[i]); 129 | CRn[i] = scratch[1].add(D); 130 | } 131 | 132 | bytes32 uHash = keccak256(abi.encode(u)); 133 | for (uint256 i = 0; i < nonceSet.length; i++) { 134 | require(nonceSet[i] != uHash, "Nonce already seen!"); 135 | } 136 | nonceSet.push(uHash); 137 | 138 | require(zetherVerifier.verifyTransfer(CLn, CRn, C, D, y, lastGlobalUpdate, u, proof), "Transfer proof verification failed!"); 139 | 140 | emit TransferOccurred(y, beneficiary); 141 | } 142 | 143 | function burn(Utils.G1Point memory y, uint256 bTransfer, Utils.G1Point memory u, bytes memory proof) public { 144 | bytes32 yHash = keccak256(abi.encode(y)); 145 | require(registered(yHash), "Account not yet registered."); 146 | rollOver(yHash); 147 | 148 | require(0 <= bTransfer && bTransfer <= MAX, "Transfer amount out of range."); 149 | Utils.G1Point[2] memory scratch = pending[yHash]; 150 | pending[yHash][0] = scratch[0].add(Utils.g().mul(bTransfer.neg())); 151 | 152 | scratch = acc[yHash]; // simulate debit of acc---just for use in verification, won't be applied 153 | scratch[0] = scratch[0].add(Utils.g().mul(bTransfer.neg())); 154 | bytes32 uHash = keccak256(abi.encode(u)); 155 | for (uint256 i = 0; i < nonceSet.length; i++) { 156 | require(nonceSet[i] != uHash, "Nonce already seen!"); 157 | } 158 | nonceSet.push(uHash); 159 | 160 | require(burnVerifier.verifyBurn(scratch[0], scratch[1], y, lastGlobalUpdate, u, msg.sender, proof), "Burn proof verification failed!"); 161 | require(coin.transfer(msg.sender, bTransfer), "This shouldn't fail... Something went severely wrong."); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /packages/protocol/contracts/ZetherVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache License 2.0 2 | pragma solidity ^0.7.0; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "./Utils.sol"; 6 | import "./InnerProductVerifier.sol"; 7 | 8 | contract ZetherVerifier { 9 | using Utils for uint256; 10 | using Utils for Utils.G1Point; 11 | 12 | uint256 constant UNITY = 0x14a3074b02521e3b1ed9852e5028452693e87be4e910500c7ba9bbddb2f46edd; // primitive 2^28th root of unity modulo q. 13 | uint256 constant TWO_INV = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000001; // 2^{-1} modulo q 14 | 15 | InnerProductVerifier ip; 16 | uint256 public constant fee = 0; // set this to be the "transaction fee". can be any integer under MAX. 17 | 18 | struct ZetherStatement { 19 | Utils.G1Point[] CLn; 20 | Utils.G1Point[] CRn; 21 | Utils.G1Point[] C; 22 | Utils.G1Point D; 23 | Utils.G1Point[] y; 24 | uint256 epoch; 25 | Utils.G1Point u; 26 | } 27 | 28 | struct ZetherProof { 29 | Utils.G1Point BA; 30 | Utils.G1Point BS; 31 | Utils.G1Point A; 32 | Utils.G1Point B; 33 | 34 | Utils.G1Point[] CLnG; 35 | Utils.G1Point[] CRnG; 36 | Utils.G1Point[] C_0G; 37 | Utils.G1Point[] DG; 38 | Utils.G1Point[] y_0G; 39 | Utils.G1Point[] gG; 40 | Utils.G1Point[] C_XG; 41 | Utils.G1Point[] y_XG; 42 | 43 | uint256[] f; 44 | uint256 z_A; 45 | 46 | Utils.G1Point T_1; 47 | Utils.G1Point T_2; 48 | uint256 tHat; 49 | uint256 mu; 50 | 51 | uint256 c; 52 | uint256 s_sk; 53 | uint256 s_r; 54 | uint256 s_b; 55 | uint256 s_tau; 56 | 57 | InnerProductVerifier.InnerProductProof ipProof; 58 | } 59 | 60 | constructor(address _ip) { 61 | ip = InnerProductVerifier(_ip); 62 | } 63 | 64 | function verifyTransfer(Utils.G1Point[] memory CLn, Utils.G1Point[] memory CRn, Utils.G1Point[] memory C, Utils.G1Point memory D, Utils.G1Point[] memory y, uint256 epoch, Utils.G1Point memory u, bytes memory proof) public view returns (bool) { 65 | ZetherStatement memory statement; 66 | statement.CLn = CLn; // do i need to allocate / set size?! 67 | statement.CRn = CRn; 68 | statement.C = C; 69 | statement.D = D; 70 | statement.y = y; 71 | statement.epoch = epoch; 72 | statement.u = u; 73 | ZetherProof memory zetherProof = unserialize(proof); 74 | return verify(statement, zetherProof); 75 | } 76 | 77 | struct ZetherAuxiliaries { 78 | uint256 y; 79 | uint256[64] ys; 80 | uint256 z; 81 | uint256[2] zs; // [z^2, z^3] 82 | uint256[64] twoTimesZSquared; 83 | uint256 zSum; 84 | uint256 x; 85 | uint256 t; 86 | uint256 k; 87 | Utils.G1Point tEval; 88 | } 89 | 90 | struct SigmaAuxiliaries { 91 | uint256 c; 92 | Utils.G1Point A_y; 93 | Utils.G1Point A_D; 94 | Utils.G1Point A_b; 95 | Utils.G1Point A_X; 96 | Utils.G1Point A_t; 97 | Utils.G1Point gEpoch; 98 | Utils.G1Point A_u; 99 | } 100 | 101 | struct AnonAuxiliaries { 102 | uint256 m; 103 | uint256 N; 104 | uint256 v; 105 | uint256 w; 106 | uint256 vPow; 107 | uint256 wPow; 108 | uint256[2][] f; // could just allocate extra space in the proof? 109 | uint256[2][] r; // each poly is an array of length N. evaluations of prods 110 | Utils.G1Point temp; 111 | Utils.G1Point CLnR; 112 | Utils.G1Point CRnR; 113 | Utils.G1Point[2][] CR; 114 | Utils.G1Point[2][] yR; 115 | Utils.G1Point C_XR; 116 | Utils.G1Point y_XR; 117 | Utils.G1Point gR; 118 | Utils.G1Point DR; 119 | } 120 | 121 | struct IPAuxiliaries { 122 | Utils.G1Point P; 123 | Utils.G1Point u_x; 124 | Utils.G1Point[] hPrimes; 125 | Utils.G1Point hPrimeSum; 126 | uint256 o; 127 | } 128 | 129 | function gSum() internal pure returns (Utils.G1Point memory) { 130 | return Utils.G1Point(0x00715f13ea08d6b51bedcde3599d8e12163e090921309d5aafc9b5bfaadbcda0, 0x27aceab598af7bf3d16ca9d40fe186c489382c21bb9d22b19cb3af8b751b959f); 131 | } 132 | 133 | function verify(ZetherStatement memory statement, ZetherProof memory proof) internal view returns (bool) { 134 | uint256 statementHash = uint256(keccak256(abi.encode(statement.CLn, statement.CRn, statement.C, statement.D, statement.y, statement.epoch))).mod(); 135 | AnonAuxiliaries memory anonAuxiliaries; 136 | anonAuxiliaries.v = uint256(keccak256(abi.encode(statementHash, proof.BA, proof.BS, proof.A, proof.B))).mod(); 137 | anonAuxiliaries.w = uint256(keccak256(abi.encode(anonAuxiliaries.v, proof.CLnG, proof.CRnG, proof.C_0G, proof.DG, proof.y_0G, proof.gG, proof.C_XG, proof.y_XG))).mod(); 138 | anonAuxiliaries.m = proof.f.length / 2; 139 | anonAuxiliaries.N = 1 << anonAuxiliaries.m; 140 | anonAuxiliaries.f = new uint256[2][](2 * anonAuxiliaries.m); 141 | for (uint256 k = 0; k < 2 * anonAuxiliaries.m; k++) { 142 | anonAuxiliaries.f[k][1] = proof.f[k]; 143 | anonAuxiliaries.f[k][0] = anonAuxiliaries.w.sub(proof.f[k]); // is it wasteful to store / keep all these in memory? 144 | } 145 | 146 | for (uint256 k = 0; k < 2 * anonAuxiliaries.m; k++) { 147 | anonAuxiliaries.temp = anonAuxiliaries.temp.add(ip.gs(k).mul(anonAuxiliaries.f[k][1])); 148 | anonAuxiliaries.temp = anonAuxiliaries.temp.add(ip.hs(k).mul(anonAuxiliaries.f[k][1].mul(anonAuxiliaries.f[k][0]))); 149 | } 150 | anonAuxiliaries.temp = anonAuxiliaries.temp.add(ip.hs(2 * anonAuxiliaries.m).mul(anonAuxiliaries.f[0][1].mul(anonAuxiliaries.f[anonAuxiliaries.m][1])).add(ip.hs(2 * anonAuxiliaries.m + 1).mul(anonAuxiliaries.f[0][0].mul(anonAuxiliaries.f[anonAuxiliaries.m][0])))); 151 | require(proof.B.mul(anonAuxiliaries.w).add(proof.A).eq(anonAuxiliaries.temp.add(Utils.h().mul(proof.z_A))), "Recovery failure for B^w * A."); 152 | 153 | anonAuxiliaries.r = assemblePolynomials(anonAuxiliaries.f); 154 | 155 | anonAuxiliaries.CR = assembleConvolutions(anonAuxiliaries.r, statement.C); 156 | anonAuxiliaries.yR = assembleConvolutions(anonAuxiliaries.r, statement.y); 157 | for (uint256 i = 0; i < anonAuxiliaries.N; i++) { 158 | anonAuxiliaries.CLnR = anonAuxiliaries.CLnR.add(statement.CLn[i].mul(anonAuxiliaries.r[i][0])); 159 | anonAuxiliaries.CRnR = anonAuxiliaries.CRnR.add(statement.CRn[i].mul(anonAuxiliaries.r[i][0])); 160 | } 161 | anonAuxiliaries.vPow = 1; 162 | for (uint256 i = 0; i < anonAuxiliaries.N; i++) { 163 | anonAuxiliaries.C_XR = anonAuxiliaries.C_XR.add(anonAuxiliaries.CR[i / 2][i % 2].mul(anonAuxiliaries.vPow)); 164 | anonAuxiliaries.y_XR = anonAuxiliaries.y_XR.add(anonAuxiliaries.yR[i / 2][i % 2].mul(anonAuxiliaries.vPow)); 165 | if (i > 0) { 166 | anonAuxiliaries.vPow = anonAuxiliaries.vPow.mul(anonAuxiliaries.v); 167 | } 168 | } 169 | anonAuxiliaries.wPow = 1; 170 | for (uint256 k = 0; k < anonAuxiliaries.m; k++) { 171 | anonAuxiliaries.CLnR = anonAuxiliaries.CLnR.add(proof.CLnG[k].mul(anonAuxiliaries.wPow.neg())); 172 | anonAuxiliaries.CRnR = anonAuxiliaries.CRnR.add(proof.CRnG[k].mul(anonAuxiliaries.wPow.neg())); 173 | anonAuxiliaries.CR[0][0] = anonAuxiliaries.CR[0][0].add(proof.C_0G[k].mul(anonAuxiliaries.wPow.neg())); 174 | anonAuxiliaries.DR = anonAuxiliaries.DR.add(proof.DG[k].mul(anonAuxiliaries.wPow.neg())); 175 | anonAuxiliaries.yR[0][0] = anonAuxiliaries.yR[0][0].add(proof.y_0G[k].mul(anonAuxiliaries.wPow.neg())); 176 | anonAuxiliaries.gR = anonAuxiliaries.gR.add(proof.gG[k].mul(anonAuxiliaries.wPow.neg())); 177 | anonAuxiliaries.C_XR = anonAuxiliaries.C_XR.add(proof.C_XG[k].mul(anonAuxiliaries.wPow.neg())); 178 | anonAuxiliaries.y_XR = anonAuxiliaries.y_XR.add(proof.y_XG[k].mul(anonAuxiliaries.wPow.neg())); 179 | 180 | anonAuxiliaries.wPow = anonAuxiliaries.wPow.mul(anonAuxiliaries.w); 181 | } 182 | anonAuxiliaries.DR = anonAuxiliaries.DR.add(statement.D.mul(anonAuxiliaries.wPow)); 183 | anonAuxiliaries.gR = anonAuxiliaries.gR.add(Utils.g().mul(anonAuxiliaries.wPow)); 184 | anonAuxiliaries.C_XR = anonAuxiliaries.C_XR.add(Utils.g().mul(fee.mul(anonAuxiliaries.wPow))); // this line is new 185 | 186 | ZetherAuxiliaries memory zetherAuxiliaries; 187 | zetherAuxiliaries.y = uint256(keccak256(abi.encode(anonAuxiliaries.w))).mod(); 188 | zetherAuxiliaries.ys[0] = 1; 189 | zetherAuxiliaries.k = 1; 190 | for (uint256 i = 1; i < 64; i++) { 191 | zetherAuxiliaries.ys[i] = zetherAuxiliaries.ys[i - 1].mul(zetherAuxiliaries.y); 192 | zetherAuxiliaries.k = zetherAuxiliaries.k.add(zetherAuxiliaries.ys[i]); 193 | } 194 | zetherAuxiliaries.z = uint256(keccak256(abi.encode(zetherAuxiliaries.y))).mod(); 195 | zetherAuxiliaries.zs[0] = zetherAuxiliaries.z.mul(zetherAuxiliaries.z); 196 | zetherAuxiliaries.zs[1] = zetherAuxiliaries.zs[0].mul(zetherAuxiliaries.z); 197 | zetherAuxiliaries.zSum = zetherAuxiliaries.zs[0].add(zetherAuxiliaries.zs[1]).mul(zetherAuxiliaries.z); 198 | zetherAuxiliaries.k = zetherAuxiliaries.k.mul(zetherAuxiliaries.z.sub(zetherAuxiliaries.zs[0])).sub(zetherAuxiliaries.zSum.mul(1 << 32).sub(zetherAuxiliaries.zSum)); 199 | zetherAuxiliaries.t = proof.tHat.sub(zetherAuxiliaries.k); // t = tHat - delta(y, z) 200 | for (uint256 i = 0; i < 32; i++) { 201 | zetherAuxiliaries.twoTimesZSquared[i] = zetherAuxiliaries.zs[0].mul(1 << i); 202 | zetherAuxiliaries.twoTimesZSquared[i + 32] = zetherAuxiliaries.zs[1].mul(1 << i); 203 | } 204 | 205 | zetherAuxiliaries.x = uint256(keccak256(abi.encode(zetherAuxiliaries.z, proof.T_1, proof.T_2))).mod(); 206 | zetherAuxiliaries.tEval = proof.T_1.mul(zetherAuxiliaries.x).add(proof.T_2.mul(zetherAuxiliaries.x.mul(zetherAuxiliaries.x))); // replace with "commit"? 207 | 208 | SigmaAuxiliaries memory sigmaAuxiliaries; 209 | sigmaAuxiliaries.A_y = anonAuxiliaries.gR.mul(proof.s_sk).add(anonAuxiliaries.yR[0][0].mul(proof.c.neg())); 210 | sigmaAuxiliaries.A_D = Utils.g().mul(proof.s_r).add(statement.D.mul(proof.c.neg())); // add(mul(anonAuxiliaries.gR, proof.s_r), mul(anonAuxiliaries.DR, proof.c.neg())); 211 | sigmaAuxiliaries.A_b = Utils.g().mul(proof.s_b).add(anonAuxiliaries.DR.mul(zetherAuxiliaries.zs[0].neg()).add(anonAuxiliaries.CRnR.mul(zetherAuxiliaries.zs[1])).mul(proof.s_sk).add(anonAuxiliaries.CR[0][0].add(Utils.g().mul(fee.mul(anonAuxiliaries.wPow))).mul(zetherAuxiliaries.zs[0].neg()).add(anonAuxiliaries.CLnR.mul(zetherAuxiliaries.zs[1])).mul(proof.c.neg()))); 212 | sigmaAuxiliaries.A_X = anonAuxiliaries.y_XR.mul(proof.s_r).add(anonAuxiliaries.C_XR.mul(proof.c.neg())); 213 | sigmaAuxiliaries.A_t = Utils.g().mul(zetherAuxiliaries.t).add(zetherAuxiliaries.tEval.neg()).mul(proof.c.mul(anonAuxiliaries.wPow)).add(Utils.h().mul(proof.s_tau)).add(Utils.g().mul(proof.s_b.neg())); 214 | sigmaAuxiliaries.gEpoch = Utils.mapInto("Zether", statement.epoch); 215 | sigmaAuxiliaries.A_u = sigmaAuxiliaries.gEpoch.mul(proof.s_sk).add(statement.u.mul(proof.c.neg())); 216 | 217 | sigmaAuxiliaries.c = uint256(keccak256(abi.encode(zetherAuxiliaries.x, sigmaAuxiliaries.A_y, sigmaAuxiliaries.A_D, sigmaAuxiliaries.A_b, sigmaAuxiliaries.A_X, sigmaAuxiliaries.A_t, sigmaAuxiliaries.A_u))).mod(); 218 | require(sigmaAuxiliaries.c == proof.c, "Sigma protocol challenge equality failure."); 219 | 220 | IPAuxiliaries memory ipAuxiliaries; 221 | ipAuxiliaries.o = uint256(keccak256(abi.encode(sigmaAuxiliaries.c))).mod(); 222 | ipAuxiliaries.u_x = Utils.h().mul(ipAuxiliaries.o); 223 | ipAuxiliaries.hPrimes = new Utils.G1Point[](64); 224 | for (uint256 i = 0; i < 64; i++) { 225 | ipAuxiliaries.hPrimes[i] = ip.hs(i).mul(zetherAuxiliaries.ys[i].inv()); 226 | ipAuxiliaries.hPrimeSum = ipAuxiliaries.hPrimeSum.add(ipAuxiliaries.hPrimes[i].mul(zetherAuxiliaries.ys[i].mul(zetherAuxiliaries.z).add(zetherAuxiliaries.twoTimesZSquared[i]))); 227 | } 228 | ipAuxiliaries.P = proof.BA.add(proof.BS.mul(zetherAuxiliaries.x)).add(gSum().mul(zetherAuxiliaries.z.neg())).add(ipAuxiliaries.hPrimeSum); 229 | ipAuxiliaries.P = ipAuxiliaries.P.add(Utils.h().mul(proof.mu.neg())); 230 | ipAuxiliaries.P = ipAuxiliaries.P.add(ipAuxiliaries.u_x.mul(proof.tHat)); 231 | require(ip.verifyInnerProduct(ipAuxiliaries.hPrimes, ipAuxiliaries.u_x, ipAuxiliaries.P, proof.ipProof, ipAuxiliaries.o), "Inner product proof verification failed."); 232 | 233 | return true; 234 | } 235 | 236 | function assemblePolynomials(uint256[2][] memory f) internal pure returns (uint256[2][] memory result) { 237 | // f is a 2m-by-2 array... containing the f's and x - f's, twice (i.e., concatenated). 238 | // output contains two "rows", each of length N. 239 | uint256 m = f.length / 2; 240 | uint256 N = 1 << m; 241 | result = new uint256[2][](N); 242 | for (uint256 j = 0; j < 2; j++) { 243 | result[0][j] = 1; 244 | for (uint256 k = 0; k < m; k++) { 245 | for (uint256 i = 0; i < N; i += 1 << m - k) { 246 | result[i + (1 << m - 1 - k)][j] = result[i][j].mul(f[j * m + m - 1 - k][1]); 247 | result[i][j] = result[i][j].mul(f[j * m + m - 1 - k][0]); 248 | } 249 | } 250 | } 251 | } 252 | 253 | function assembleConvolutions(uint256[2][] memory exponent, Utils.G1Point[] memory base) internal view returns (Utils.G1Point[2][] memory result) { 254 | // exponent is two "rows" (actually columns). 255 | // will return two rows, each of half the length of the exponents; 256 | // namely, we will return the Hadamards of "base" by the even circular shifts of "exponent"'s rows. 257 | uint256 size = exponent.length; 258 | uint256 half = size / 2; 259 | result = new Utils.G1Point[2][](half); // assuming that this is necessary even when return is declared up top 260 | uint256 omega = UNITY.exp((1 << 28) / size); // wasteful: using exp for all 256-bits, though we only need 28 (at most!) 261 | uint256 omega_inv = omega.mul(omega).inv(); // also square it. inverse fft will be half as big 262 | uint256[] memory omegas = new uint256[](half); 263 | // TODO edge case when there are no decoys - size = 2 264 | // The inverses array is only used for the fft call with inverse=true however since the inverse_fft array has 265 | // size=1 the fft call returns the input array (without any further processing). Maybe size=2 should be treated 266 | // separately (and possibly simplified). 267 | uint256 halfHalf = half == 1 ? 1 : half / 2; 268 | uint256[] memory inverses = new uint256[](halfHalf); // if it's not an integer, will this still work nicely? 269 | omegas[0] = 1; 270 | inverses[0] = 1; 271 | for (uint256 i = 1; i < half; i++) omegas[i] = omegas[i - 1].mul(omega); 272 | for (uint256 i = 1; i < halfHalf; i++) inverses[i] = inverses[i - 1].mul(omega_inv); 273 | Utils.G1Point[] memory base_fft = fft(base, omegas, false); // could precompute UNITY.inv(), but... have to exp it anyway 274 | uint256[] memory exponent_fft = new uint256[](size); 275 | for (uint256 j = 0; j < 2; j++) { 276 | for (uint256 i = 0; i < size; i++) exponent_fft[i] = exponent[(size - i) % size][j]; // convolutional flip plus copy 277 | 278 | exponent_fft = fft(exponent_fft, omegas); 279 | Utils.G1Point[] memory inverse_fft = new Utils.G1Point[](half); 280 | for (uint256 i = 0; i < half; i++) { // break up into two statements for ease of reading 281 | inverse_fft[i] = inverse_fft[i].add(base_fft[i].mul(exponent_fft[i].mul(TWO_INV))); 282 | inverse_fft[i] = inverse_fft[i].add(base_fft[i + half].mul(exponent_fft[i + half].mul(TWO_INV))); 283 | } 284 | 285 | inverse_fft = fft(inverse_fft, inverses, true); // square, because half as big. 286 | for (uint256 i = 0; i < half; i++) result[i][j] = inverse_fft[i]; 287 | } 288 | } 289 | 290 | function fft(Utils.G1Point[] memory input, uint256[] memory omegas, bool inverse) internal view returns (Utils.G1Point[] memory result) { 291 | uint256 size = input.length; 292 | if (size == 1) return input; 293 | require(size % 2 == 0, "Input size is not a power of 2!"); 294 | 295 | Utils.G1Point[] memory even = fft(extract(input, 0), extract(omegas, 0), inverse); 296 | Utils.G1Point[] memory odd = fft(extract(input, 1), extract(omegas, 0), inverse); 297 | result = new Utils.G1Point[](size); 298 | for (uint256 i = 0; i < size / 2; i++) { 299 | Utils.G1Point memory temp = odd[i].mul(omegas[i]); 300 | result[i] = even[i].add(temp); 301 | result[i + size / 2] = even[i].add(temp.neg()); 302 | if (inverse) { // could probably "delay" the successive multiplications by 2 up the recursion. 303 | result[i] = result[i].mul(TWO_INV); 304 | result[i + size / 2] = result[i + size / 2].mul(TWO_INV); 305 | } 306 | } 307 | } 308 | 309 | function extract(Utils.G1Point[] memory input, uint256 parity) internal pure returns (Utils.G1Point[] memory result) { 310 | result = new Utils.G1Point[](input.length / 2); 311 | for (uint256 i = 0; i < input.length / 2; i++) { 312 | result[i] = input[2 * i + parity]; 313 | } 314 | } 315 | 316 | function fft(uint256[] memory input, uint256[] memory omegas) internal view returns (uint256[] memory result) { 317 | uint256 size = input.length; 318 | if (size == 1) return input; 319 | require(size % 2 == 0, "Input size is not a power of 2!"); 320 | 321 | uint256[] memory even = fft(extract(input, 0), extract(omegas, 0)); 322 | uint256[] memory odd = fft(extract(input, 1), extract(omegas, 0)); 323 | result = new uint256[](size); 324 | for (uint256 i = 0; i < size / 2; i++) { 325 | uint256 temp = odd[i].mul(omegas[i]); 326 | result[i] = even[i].add(temp); 327 | result[i + size / 2] = even[i].sub(temp); 328 | } 329 | } 330 | 331 | function extract(uint256[] memory input, uint256 parity) internal pure returns (uint256[] memory result) { 332 | result = new uint256[](input.length / 2); 333 | for (uint256 i = 0; i < input.length / 2; i++) { 334 | result[i] = input[2 * i + parity]; 335 | } 336 | } 337 | 338 | function unserialize(bytes memory arr) internal pure returns (ZetherProof memory proof) { 339 | proof.BA = Utils.G1Point(Utils.slice(arr, 0), Utils.slice(arr, 32)); 340 | proof.BS = Utils.G1Point(Utils.slice(arr, 64), Utils.slice(arr, 96)); 341 | proof.A = Utils.G1Point(Utils.slice(arr, 128), Utils.slice(arr, 160)); 342 | proof.B = Utils.G1Point(Utils.slice(arr, 192), Utils.slice(arr, 224)); 343 | 344 | uint256 m = (arr.length - 1472) / 576; 345 | proof.CLnG = new Utils.G1Point[](m); 346 | proof.CRnG = new Utils.G1Point[](m); 347 | proof.C_0G = new Utils.G1Point[](m); 348 | proof.DG = new Utils.G1Point[](m); 349 | proof.y_0G = new Utils.G1Point[](m); 350 | proof.gG = new Utils.G1Point[](m); 351 | proof.C_XG = new Utils.G1Point[](m); 352 | proof.y_XG = new Utils.G1Point[](m); 353 | proof.f = new uint256[](2 * m); 354 | for (uint256 k = 0; k < m; k++) { 355 | proof.CLnG[k] = Utils.G1Point(Utils.slice(arr, 256 + k * 64), Utils.slice(arr, 288 + k * 64)); 356 | proof.CRnG[k] = Utils.G1Point(Utils.slice(arr, 256 + (m + k) * 64), Utils.slice(arr, 288 + (m + k) * 64)); 357 | proof.C_0G[k] = Utils.G1Point(Utils.slice(arr, 256 + m * 128 + k * 64), Utils.slice(arr, 288 + m * 128 + k * 64)); 358 | proof.DG[k] = Utils.G1Point(Utils.slice(arr, 256 + m * 192 + k * 64), Utils.slice(arr, 288 + m * 192 + k * 64)); 359 | proof.y_0G[k] = Utils.G1Point(Utils.slice(arr, 256 + m * 256 + k * 64), Utils.slice(arr, 288 + m * 256 + k * 64)); 360 | proof.gG[k] = Utils.G1Point(Utils.slice(arr, 256 + m * 320 + k * 64), Utils.slice(arr, 288 + m * 320 + k * 64)); 361 | proof.C_XG[k] = Utils.G1Point(Utils.slice(arr, 256 + m * 384 + k * 64), Utils.slice(arr, 288 + m * 384 + k * 64)); 362 | proof.y_XG[k] = Utils.G1Point(Utils.slice(arr, 256 + m * 448 + k * 64), Utils.slice(arr, 288 + m * 448 + k * 64)); 363 | proof.f[k] = uint256(Utils.slice(arr, 256 + m * 512 + k * 32)); 364 | proof.f[k + m] = uint256(Utils.slice(arr, 256 + m * 544 + k * 32)); 365 | } 366 | uint256 starting = m * 576; 367 | proof.z_A = uint256(Utils.slice(arr, 256 + starting)); 368 | 369 | proof.T_1 = Utils.G1Point(Utils.slice(arr, 288 + starting), Utils.slice(arr, 320 + starting)); 370 | proof.T_2 = Utils.G1Point(Utils.slice(arr, 352 + starting), Utils.slice(arr, 384 + starting)); 371 | proof.tHat = uint256(Utils.slice(arr, 416 + starting)); 372 | proof.mu = uint256(Utils.slice(arr, 448 + starting)); 373 | 374 | proof.c = uint256(Utils.slice(arr, 480 + starting)); 375 | proof.s_sk = uint256(Utils.slice(arr, 512 + starting)); 376 | proof.s_r = uint256(Utils.slice(arr, 544 + starting)); 377 | proof.s_b = uint256(Utils.slice(arr, 576 + starting)); 378 | proof.s_tau = uint256(Utils.slice(arr, 608 + starting)); 379 | 380 | InnerProductVerifier.InnerProductProof memory ipProof; 381 | ipProof.L = new Utils.G1Point[](6); 382 | ipProof.R = new Utils.G1Point[](6); 383 | for (uint256 i = 0; i < 6; i++) { // 2^6 = 64. 384 | ipProof.L[i] = Utils.G1Point(Utils.slice(arr, 640 + starting + i * 64), Utils.slice(arr, 672 + starting + i * 64)); 385 | ipProof.R[i] = Utils.G1Point(Utils.slice(arr, 640 + starting + (6 + i) * 64), Utils.slice(arr, 672 + starting + (6 + i) * 64)); 386 | } 387 | ipProof.a = uint256(Utils.slice(arr, 640 + starting + 6 * 128)); 388 | ipProof.b = uint256(Utils.slice(arr, 672 + starting + 6 * 128)); 389 | proof.ipProof = ipProof; 390 | 391 | return proof; 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /packages/protocol/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = (deployer) => { 4 | deployer.deploy(Migrations); 5 | }; -------------------------------------------------------------------------------- /packages/protocol/migrations/2_deploy_zsc.js: -------------------------------------------------------------------------------- 1 | var InnerProductVerifier = artifacts.require("InnerProductVerifier"); 2 | var BurnVerifier = artifacts.require("BurnVerifier"); 3 | var ZetherVerifier = artifacts.require("ZetherVerifier"); 4 | var CashToken = artifacts.require("CashToken"); 5 | var ZSC = artifacts.require("ZSC"); 6 | 7 | module.exports = (deployer) => { 8 | return Promise.all([ 9 | deployer.deploy(CashToken), 10 | deployer.deploy(InnerProductVerifier, { gas: 6721975 }).then(() => Promise.all([ 11 | deployer.deploy(ZetherVerifier, InnerProductVerifier.address, { gas: 6721975 }), 12 | deployer.deploy(BurnVerifier, InnerProductVerifier.address, { gas: 6721975 }) 13 | ])) 14 | ]).then(() => deployer.deploy(ZSC, CashToken.address, ZetherVerifier.address, BurnVerifier.address, 6)); 15 | } -------------------------------------------------------------------------------- /packages/protocol/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@anonymous-zether/protocol", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "./lib", 6 | "dependencies": { 7 | "@truffle/contract": "^4.5.21", 8 | "bn.js": "^5.1.1", 9 | "elliptic": "^6.5.1", 10 | "eslint-import-resolver-lerna": "^1.1.0", 11 | "web3": "^1.7.4" 12 | }, 13 | "devDependencies": { 14 | "@openzeppelin/contracts": "3.1.0-solc-0.7", 15 | "eslint": "^5.16.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/protocol/test/zsc.js: -------------------------------------------------------------------------------- 1 | const CashToken = artifacts.require("CashToken"); 2 | const ZSC = artifacts.require("ZSC"); 3 | const Client = require('../../anonymous.js/src/client.js'); 4 | 5 | contract("ZSC", async (accounts) => { 6 | let alice; // will reuse... 7 | let bob; 8 | let carol; 9 | let dave; 10 | let miner; 11 | 12 | it("should allow minting and approving", async () => { 13 | const cash = await CashToken.deployed(); 14 | const zsc = await ZSC.deployed(); 15 | await cash.mint(accounts[0], 1000); 16 | await cash.approve(zsc.contract._address, 1000); 17 | const balance = await cash.balanceOf.call(accounts[0]); 18 | assert.equal( 19 | balance, 20 | 1000, 21 | "Minting failed" 22 | ); 23 | }); 24 | 25 | it("should allow initialization", async () => { 26 | const zsc = await ZSC.deployed(); 27 | alice = new Client(web3, zsc.contract, accounts[0]); 28 | await alice.register(); 29 | }); 30 | 31 | it("should allow funding", async () => { 32 | await alice.deposit(100); 33 | }); 34 | 35 | it("should allow withdrawing", async () => { 36 | await alice.withdraw(10); 37 | }); 38 | 39 | it("should allow transferring (2 decoys and miner)", async () => { 40 | const zsc = await ZSC.deployed(); 41 | bob = new Client(web3, zsc.contract, accounts[0]); 42 | carol = new Client(web3, zsc.contract, accounts[0]); 43 | dave = new Client(web3, zsc.contract, accounts[0]); 44 | miner = new Client(web3, zsc.contract, accounts[0]); 45 | await Promise.all([bob.register(), carol.register(), dave.register(), miner.register()]); 46 | alice.friends.add("Bob", bob.account.public()); 47 | alice.friends.add("Carol", carol.account.public()); 48 | alice.friends.add("Dave", dave.account.public()); 49 | alice.friends.add("Miner", miner.account.public()); 50 | await alice.transfer("Bob", 10, ["Carol", "Dave"], "Miner"); 51 | await new Promise((resolve) => setTimeout(resolve, 100)); 52 | assert.equal( 53 | bob.account.balance(), 54 | 10, 55 | "Transfer failed" 56 | ); 57 | const fee = await zsc.fee.call(); 58 | assert.equal( 59 | miner.account.balance(), 60 | fee, 61 | "Fees failed" 62 | ); 63 | }); 64 | 65 | it("should allow transferring (2 decoys and NO miner)", async () => { 66 | const zsc = await ZSC.deployed(); 67 | await alice.transfer("Bob", 10, ["Carol", "Dave"]); 68 | await new Promise((resolve) => setTimeout(resolve, 100)); 69 | assert.equal( 70 | bob.account.balance(), 71 | 20, 72 | "Transfer failed" 73 | ); 74 | }); 75 | 76 | it("should allow transferring (6 decoys and miner)", async () => { 77 | const zsc = await ZSC.deployed(); 78 | bob1 = new Client(web3, zsc.contract, accounts[0]); 79 | carol1 = new Client(web3, zsc.contract, accounts[0]); 80 | dave1 = new Client(web3, zsc.contract, accounts[0]); 81 | miner1 = new Client(web3, zsc.contract, accounts[0]); 82 | await Promise.all([bob1.register(), carol1.register(), dave1.register(), miner1.register()]); 83 | alice.friends.add("Bob1", bob1.account.public()); 84 | alice.friends.add("Carol1", carol1.account.public()); 85 | alice.friends.add("Dave1", dave1.account.public()); 86 | alice.friends.add("Miner1", miner1.account.public()); 87 | await alice.transfer("Bob", 10, ["Carol", "Dave", "Bob1", "Carol1", "Dave1", "Miner1"], "Miner"); 88 | await new Promise((resolve) => setTimeout(resolve, 100)); 89 | assert.equal( 90 | bob.account.balance(), 91 | 30, 92 | "Transfer failed" 93 | ); 94 | const fee = await zsc.fee.call(); 95 | assert.equal( 96 | miner.account.balance(), 97 | fee, 98 | "Fees failed" 99 | ); 100 | }); 101 | 102 | it("should allow transferring without decoys or miner", async () => { 103 | const zsc = await ZSC.deployed(); 104 | zuza = new Client(web3, zsc.contract, accounts[0]); 105 | await zuza.register() 106 | alice.friends.add("Zuza", zuza.account.public()); 107 | await alice.transfer("Zuza", 5); 108 | await new Promise((resolve) => setTimeout(resolve, 100)); 109 | assert.equal( 110 | zuza.account.balance(), 111 | 5, 112 | "Transfer failed" 113 | ); 114 | }); 115 | 116 | it("should allow transferring without decoys but with miner", async () => { 117 | await alice.transfer("Carol", 5, [], "Miner"); 118 | await new Promise((resolve) => setTimeout(resolve, 100)); 119 | assert.equal( 120 | carol.account.balance(), 121 | 5, 122 | "Transfer failed" 123 | ); 124 | }); 125 | 126 | }); -------------------------------------------------------------------------------- /packages/protocol/truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "127.0.0.1", 5 | port: 8545, // ganache 6 | gasPrice: 0, 7 | network_id: "*", // Match any network id 8 | websockets: true, 9 | }, 10 | qex: { 11 | host: "127.0.0.1", 12 | port: 22000, // node1 in quorum examples 13 | gasPrice: 0, 14 | network_id: "*", 15 | websockets: true, 16 | } 17 | }, 18 | compilers: { 19 | solc: { 20 | version: "0.7.0", 21 | } 22 | } 23 | }; 24 | --------------------------------------------------------------------------------