forked from tornadocash/tornado-cli
Support creating deposit notes offline
Co-authored-by: yoyoismee <yoyoismee@gmail.com>
This commit is contained in:
parent
378bab8fbe
commit
2854b7a12f
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ node_modules
|
|||||||
.env
|
.env
|
||||||
.idea
|
.idea
|
||||||
backup-tornado-*
|
backup-tornado-*
|
||||||
|
backup-tornadoInvoice-*
|
||||||
|
40
README.md
40
README.md
@ -8,6 +8,14 @@ Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medi
|
|||||||
### How to install tornado cli
|
### How to install tornado cli
|
||||||
Download and install [node.js](https://nodejs.org/en/download/).
|
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.
|
If you have git installed on your system, clone the master branch.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -57,6 +65,38 @@ Transaction mined in block 17036120
|
|||||||
Done
|
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
|
### List of public rpc & relayers for withdrawal
|
||||||
|
|
||||||
Infura API key fetched from https://rpc.info (Same one with Metamask)
|
Infura API key fetched from https://rpc.info (Same one with Metamask)
|
||||||
|
93
cli.js
93
cli.js
@ -180,13 +180,49 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a deposit invoice.
|
||||||
|
* @param currency Сurrency
|
||||||
|
* @param amount Deposit amount
|
||||||
|
*/
|
||||||
|
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}-${chainId}-${note}`;
|
||||||
|
console.log(`Your 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
|
* Make a deposit
|
||||||
* @param currency Сurrency
|
* @param currency Сurrency
|
||||||
* @param amount Deposit amount
|
* @param amount Deposit amount
|
||||||
*/
|
*/
|
||||||
async function deposit({ currency, 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');
|
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({
|
const deposit = createDeposit({
|
||||||
nullifier: rbigint(31),
|
nullifier: rbigint(31),
|
||||||
secret: rbigint(31)
|
secret: rbigint(31)
|
||||||
@ -195,12 +231,17 @@ async function deposit({ currency, amount }) {
|
|||||||
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`;
|
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`;
|
||||||
console.log(`Your note: ${noteString}`);
|
console.log(`Your note: ${noteString}`);
|
||||||
await backupNote({ currency, amount, netId, 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()) {
|
if (currency === netSymbol.toLowerCase()) {
|
||||||
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract' });
|
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract' });
|
||||||
await printETHBalance({ address: senderAccount, name: 'Sender account' });
|
await printETHBalance({ address: senderAccount, name: 'Sender account' });
|
||||||
const value = isTestRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 });
|
const value = isTestRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 });
|
||||||
console.log('Submitting deposit transaction');
|
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: tornadoContract._address, name: 'Tornado contract' });
|
||||||
await printETHBalance({ address: senderAccount, name: 'Sender account' });
|
await printETHBalance({ address: senderAccount, name: 'Sender account' });
|
||||||
} else {
|
} else {
|
||||||
@ -222,13 +263,15 @@ async function deposit({ currency, amount }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('Submitting deposit transaction');
|
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: tornadoContract._address, name: 'Tornado contract' });
|
||||||
await printERC20Balance({ address: senderAccount, name: 'Sender account' });
|
await printERC20Balance({ address: senderAccount, name: 'Sender account' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!commitmentNote) {
|
||||||
return noteString;
|
return noteString;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate merkle tree for a deposit.
|
* Generate merkle tree for a deposit.
|
||||||
@ -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 }) {
|
async function loadDepositData({ amount, currency, deposit }) {
|
||||||
try {
|
try {
|
||||||
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount });
|
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount });
|
||||||
@ -1257,6 +1323,27 @@ async function main() {
|
|||||||
.option('-R, --relayer <URL>', 'Withdraw via relayer')
|
.option('-R, --relayer <URL>', 'Withdraw via relayer')
|
||||||
.option('-T, --tor <PORT>', 'Optional tor port')
|
.option('-T, --tor <PORT>', 'Optional tor port')
|
||||||
.option('-L, --local', 'Local Node - Does not submit signed transaction to the node');
|
.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
|
program
|
||||||
.command('deposit <currency> <amount>')
|
.command('deposit <currency> <amount>')
|
||||||
.description(
|
.description(
|
||||||
|
Loading…
Reference in New Issue
Block a user