tornado.spec.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. // SPDX-License-Identifier: Apache-2.0
  2. // Tests against the tornado cash core contracts.
  3. // The tornado contracts used here contain minor mechanical changes to work fine on Polkadot.
  4. // The ZK-SNARK setup is the same as ETH Tornado on mainnet.
  5. // On the node, the MiMC sponge hash (available as EVM bytecode) and bn128 curve operations
  6. // (precompiled contracts on Ethereum) are expected to be implemented as chain extensions.
  7. import expect from 'expect';
  8. import { weight, createConnection, deploy, transaction, aliceKeypair, daveKeypair, debug_buffer, } from './index';
  9. import { ContractPromise } from '@polkadot/api-contract';
  10. import { ApiPromise } from '@polkadot/api';
  11. import { KeyringPair } from '@polkadot/keyring/types';
  12. import { createNote, init_snark, toHex, withdraw, } from './tornado/tornado'
  13. type Deposit = { noteString: string; commitment: string; };
  14. let deposits: Deposit[];
  15. const denomination = 1000000000000n;
  16. const merkle_tree_height = 20;
  17. function addressToBigInt(uint8Array: Uint8Array): bigint {
  18. let result = BigInt(0);
  19. for (let i = 0; i < uint8Array.length; i++) {
  20. result <<= BigInt(8); // Left shift by 8 bits
  21. result += BigInt(uint8Array[i]); // Add the current byte
  22. }
  23. return result;
  24. }
  25. // Generate a ZK proof needed to withdraw funds. Uses the deposit at the given `index`.
  26. async function generateProof(recipient: KeyringPair, index: number): Promise<string[]> {
  27. const to = addressToBigInt(recipient.addressRaw);
  28. // In production, we'd fetch and parse all events, which is too cumbersome for this PoC.
  29. const leaves = deposits.map(e => e.commitment);
  30. const proof = await withdraw(to, deposits[index].noteString, leaves);
  31. return [
  32. proof.proof,
  33. proof.args[0], // Merkle root
  34. proof.args[1], // Nullifier hash
  35. toHex(to), // The contract will mod it over the finite field
  36. ];
  37. }
  38. describe('Deploy the tornado contract, create 2 deposits and withdraw them afterwards', () => {
  39. let conn: ApiPromise;
  40. let tornado: ContractPromise;
  41. let alice: KeyringPair;
  42. let dave: KeyringPair;
  43. before(async function () {
  44. alice = aliceKeypair();
  45. dave = daveKeypair();
  46. // Deploy hasher, verifier and tornado contracts
  47. conn = await createConnection();
  48. const hasher_contract = await deploy(conn, alice, 'Hasher.contract', 0n);
  49. const verifier_contract = await deploy(conn, alice, 'Verifier.contract', 0n);
  50. const parameters =
  51. [
  52. verifier_contract.address,
  53. hasher_contract.address,
  54. denomination,
  55. merkle_tree_height
  56. ];
  57. const tornado_contract = await deploy(conn, alice, 'NativeTornado.contract', 0n, ...parameters);
  58. tornado = new ContractPromise(conn, tornado_contract.abi, tornado_contract.address);
  59. // Deposit some funds to the tornado contract
  60. await init_snark({});
  61. deposits = [createNote({}), createNote({})];
  62. let gasLimit = await weight(conn, tornado, 'deposit', [deposits[0].commitment], denomination);
  63. let tx = tornado.tx.deposit({ gasLimit, value: denomination }, deposits[0].commitment);
  64. await transaction(tx, alice);
  65. gasLimit = await weight(conn, tornado, 'deposit', [deposits[1].commitment], denomination);
  66. tx = tornado.tx.deposit({ gasLimit, value: denomination }, deposits[1].commitment);
  67. await transaction(tx, dave);
  68. });
  69. after(async function () {
  70. await conn.disconnect();
  71. });
  72. it('Withdraws funds deposited by alice to dave', async function () {
  73. this.timeout(50000);
  74. const { data: { free: balanceBefore } } = await conn.query.system.account(dave.address);
  75. const parameters = await generateProof(dave, 0);
  76. const gasLimit = await weight(conn, tornado, 'withdraw', parameters);
  77. await transaction(tornado.tx.withdraw({ gasLimit }, ...parameters), alice);
  78. expect(balanceBefore.toBigInt() + denomination)
  79. .toEqual((await conn.query.system.account(dave.address)).data.free.toBigInt());
  80. expect(await debug_buffer(conn, tornado, 'withdraw', parameters))
  81. .toContain('The note has been already spent');
  82. });
  83. it('Withdraws funds deposited by dave to alice', async function () {
  84. this.timeout(50000);
  85. const { data: { free: balanceBefore } } = await conn.query.system.account(dave.address);
  86. const parameters = await generateProof(dave, 1);
  87. const gasLimit = await weight(conn, tornado, 'withdraw', parameters);
  88. await transaction(tornado.tx.withdraw({ gasLimit }, ...parameters), alice);
  89. expect(balanceBefore.toBigInt() + denomination)
  90. .toEqual((await conn.query.system.account(dave.address)).data.free.toBigInt());
  91. expect(await debug_buffer(conn, tornado, 'withdraw', parameters))
  92. .toContain('The note has been already spent');
  93. });
  94. it('Fails to withdraw without a valid proof', async function () {
  95. this.timeout(50000);
  96. // Without a corresponding deposit, this merkle root should not exist yet
  97. deposits.push(createNote({}));
  98. let parameters = await generateProof(alice, 2);
  99. expect(await debug_buffer(conn, tornado, 'withdraw', parameters))
  100. .toContain('Cannot find your merkle root');
  101. const gasLimit = await weight(conn, tornado, 'deposit', [deposits[2].commitment], denomination);
  102. const tx = tornado.tx.deposit({ gasLimit, value: denomination }, deposits[2].commitment);
  103. await transaction(tx, alice);
  104. // Messing up the proof should result in a curve pairing failure
  105. parameters[0] = parameters[0].substring(0, parameters[0].length - 4) + "0000";
  106. expect(await debug_buffer(conn, tornado, 'withdraw', parameters))
  107. .toContain('pairing-opcode-failed');
  108. });
  109. });