Support creating deposit notes offline

Co-authored-by: yoyoismee <yoyoismee@gmail.com>
This commit is contained in:
Ayanami 2022-02-27 07:32:37 +09:00
parent 378bab8fbe
commit 2854b7a12f
No known key found for this signature in database
GPG Key ID: 0CABDF03077D92E4
3 changed files with 137 additions and 9 deletions

3
.gitignore vendored

@ -1,4 +1,5 @@
node_modules
.env
.idea
backup-tornado-*
backup-tornado-*
backup-tornadoInvoice-*

@ -8,6 +8,14 @@ Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medi
### How to install tornado cli
Download and install [node.js](https://nodejs.org/en/download/).
You also need to install C++ build tools in order to do 'npm install', for more information please checkout https://github.com/nodejs/node-gyp#on-unix.
For Windows: https://stackoverflow.com/a/64224475
For MacOS: Install XCode Command Line Tools
For Linux: Install make & gcc, for ubuntu `$ sudo apt-get install -y build-essentials`
If you have git installed on your system, clone the master branch.
```bash
@ -57,6 +65,38 @@ Transaction mined in block 17036120
Done
```
### (Optional) Creating Deposit Notes & Invoices offline
One of the main features of tornado-cli is that it supports creating deposit notes & invoices inside the offline computing environment.
After the private-key like notes are backed up somewhere safe, you can copy the created deposit invoices and use them to create new deposit transaction on online environment.
To create deposit notes with `createNote` command.
```bash
$ node cli.js createNote ETH 0.1 5
Your note: tornado-eth-0.1-5-0x1d9771a7b9f8b6c03d33116208ce8db1aa559d33e65d22dd2ff78375fc6b635f930536d2432b4bde0178c72cfc79d6b27023c5d9de60985f186b34c18c00
Your invoice for deposit: tornadoInvoice-eth-0.1-5-0x1b680c7dda0c2dd1b85f0fe126d49b16ed594b3cd6d5114db5f4593877a6b84f
Backed up deposit note as ./backup-tornado-eth-0.1-5-0x1d9771a7.txt
Backed up invoice as ./backup-tornadoInvoice-eth-0.1-5-0x1b680c7d.txt
```
To create corresponding deposit transaction you only need invoice value, so that the deposit note could be stored without exposed anywhere.
```bash
node cli.js depositInvoice tornadoInvoice-eth-0.1-5-0x1b680c7dda0c2dd1b85f0fe126d49b16ed594b3cd6d5114db5f4593877a6b84f --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150
Using tor network
Your remote IP address is xx.xx.xx.xx from xx.
Creating ETH 0.1 deposit for Goerli network.
Using supplied invoice for deposit
Tornado contract balance is xxx.x ETH
Sender account balance is x.xxxxxxx ETH
Submitting deposit transaction
Submitting transaction to the remote node
View transaction on block explorer https://goerli.etherscan.io/tx/0x6ded443caed8d6f2666841149532c64bee149a9a8e1070ed4c91a12dd1837747
Tornado contract balance is xxx.x ETH
Sender account balance is x.xxxxxxx ETH
```
### List of public rpc & relayers for withdrawal
Infura API key fetched from https://rpc.info (Same one with Metamask)

103
cli.js

@ -180,27 +180,68 @@ async function backupNote({ currency, amount, netId, note, noteString }) {
}
}
async function backupInvoice({ currency, amount, netId, commitmentNote, invoiceString }) {
try {
await fs.writeFileSync(`./backup-tornadoInvoice-${currency}-${amount}-${netId}-${commitmentNote.slice(0, 10)}.txt`, invoiceString, 'utf8');
console.log("Backed up invoice as", `./backup-tornadoInvoice-${currency}-${amount}-${netId}-${commitmentNote.slice(0, 10)}.txt`)
} catch (e) {
throw new Error('Writing backup invoice failed:', e)
}
}
/**
* Make a deposit
* create a deposit invoice.
* @param currency Сurrency
* @param amount Deposit amount
*/
async function deposit({ currency, amount }) {
assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit');
async function createInvoice({ currency, amount, chainId }) {
const deposit = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31)
});
const note = toHex(deposit.preimage, 62);
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`;
const noteString = `tornado-${currency}-${amount}-${chainId}-${note}`;
console.log(`Your note: ${noteString}`);
await backupNote({ currency, amount, netId, note, noteString });
const commitmentNote = toHex(deposit.commitment);
const invoiceString = `tornadoInvoice-${currency}-${amount}-${chainId}-${commitmentNote}`;
console.log(`Your invoice for deposit: ${invoiceString}`);
await backupNote({ currency, amount, netId: chainId, note, noteString });
await backupInvoice({ currency, amount, netId: chainId, commitmentNote, invoiceString });
return (noteString, invoiceString);
}
/**
* Make a deposit
* @param currency Сurrency
* @param amount Deposit amount
*/
async function deposit({ currency, amount, commitmentNote }) {
assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit');
let commitment;
if (!commitmentNote) {
console.log("Creating new random deposit note");
const deposit = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31)
});
const note = toHex(deposit.preimage, 62);
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`;
console.log(`Your note: ${noteString}`);
await backupNote({ currency, amount, netId, note, noteString });
commitment = toHex(deposit.commitment);
} else {
console.log("Using supplied invoice for deposit");
commitment = toHex(commitmentNote);
}
if (currency === netSymbol.toLowerCase()) {
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract' });
await printETHBalance({ address: senderAccount, name: 'Sender account' });
const value = isTestRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 });
console.log('Submitting deposit transaction');
await generateTransaction(contractAddress, tornado.methods.deposit(tornadoInstance, toHex(deposit.commitment), []).encodeABI(), value);
await generateTransaction(contractAddress, tornado.methods.deposit(tornadoInstance, commitment, []).encodeABI(), value);
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract' });
await printETHBalance({ address: senderAccount, name: 'Sender account' });
} else {
@ -222,12 +263,14 @@ async function deposit({ currency, amount }) {
}
console.log('Submitting deposit transaction');
await generateTransaction(contractAddress, tornado.methods.deposit(tornadoInstance, toHex(deposit.commitment), []).encodeABI());
await generateTransaction(contractAddress, tornado.methods.deposit(tornadoInstance, commitment, []).encodeABI());
await printERC20Balance({ address: tornadoContract._address, name: 'Tornado contract' });
await printERC20Balance({ address: senderAccount, name: 'Sender account' });
}
return noteString;
if(!commitmentNote) {
return noteString;
}
}
/**
@ -1051,6 +1094,29 @@ function parseNote(noteString) {
}
}
/**
* Parses Tornado.cash deposit invoice
* @param invoiceString the note
*/
function parseInvoice(invoiceString) {
const noteRegex = /tornadoInvoice-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<commitmentNote>[0-9a-fA-F]{64})/g
const match = noteRegex.exec(invoiceString)
if (!match) {
throw new Error('The note has invalid format')
}
const netId = Number(match.groups.netId)
const buf = Buffer.from(match.groups.commitmentNote, 'hex')
const commitmentNote = toHex(buf.slice(0, 32))
return {
currency: match.groups.currency,
amount: match.groups.amount,
netId,
commitmentNote
}
}
async function loadDepositData({ amount, currency, deposit }) {
try {
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount });
@ -1257,6 +1323,27 @@ async function main() {
.option('-R, --relayer <URL>', 'Withdraw via relayer')
.option('-T, --tor <PORT>', 'Optional tor port')
.option('-L, --local', 'Local Node - Does not submit signed transaction to the node');
program
.command('createNote <currency> <amount> <chainId>')
.description(
'Create deposit note and invoice, allows generating private key like deposit notes from secure, offline environment. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.'
)
.action(async (currency, amount, chainId) => {
currency = currency.toLowerCase();
await createInvoice({ currency, amount, chainId });
});
program
.command('depositInvoice <invoice>')
.description(
'Submit a deposit of invoice from default eth account and return the resulting note.'
)
.action(async (invoice) => {
torPort = program.tor;
const { currency, amount, netId, commitmentNote } = parseInvoice(invoice);
await init({ rpc: program.rpc, currency, amount, localMode: program.local });
console.log("Creating", currency.toUpperCase(), amount, "deposit for", netName, "Tornado Cash Instance");
await deposit({ currency, amount, commitmentNote });
});
program
.command('deposit <currency> <amount>')
.description(