mirror of
https://github.com/tornadocash/tornado-nova
synced 2024-02-02 14:53:56 +01:00
encrypion and full test
This commit is contained in:
parent
5bdc8d7871
commit
18571118d7
5
TODO
5
TODO
@ -7,3 +7,8 @@
|
||||
* design
|
||||
* race condition ? (sequencer or something)
|
||||
* the current trusted setup is not secure
|
||||
|
||||
How we do transaction inside pool of A amount.
|
||||
1. sort inputs by amount
|
||||
2. try to take 1 or 2 smallest inputs to satisfy A amount. Get 16 inputs if it's not possible using the same way
|
||||
3. Also you can always use transaction to merge your inputs with change (especially in 16 inputs case)
|
||||
|
41
src/index.js
41
src/index.js
@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-console */
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
const { ethers } = require('hardhat')
|
||||
const { toFixedHex, poseidonHash2, getExtDataHash, FIELD_SIZE } = require('./utils')
|
||||
const { toFixedHex, poseidonHash2, getExtDataHash, FIELD_SIZE, packEncryptedMessage } = require('./utils')
|
||||
const Utxo = require('./utxo')
|
||||
|
||||
const { prove } = require('./prover')
|
||||
@ -29,7 +29,7 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
|
||||
if (input.amount > 0) {
|
||||
const index = tree.indexOf(toFixedHex(input.getCommitment()))
|
||||
if (index < 0) {
|
||||
throw new Error(`Input commitment ${input.getCommitment()} was not found`)
|
||||
throw new Error(`Input commitment ${toFixedHex(input.getCommitment())} was not found`)
|
||||
}
|
||||
inputMerklePathIndices.push(index)
|
||||
inputMerklePathElements.push(tree.path(index).pathElements)
|
||||
@ -52,8 +52,12 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
|
||||
const extData = {
|
||||
recipient: toFixedHex(recipient, 20),
|
||||
relayer: toFixedHex(relayer, 20),
|
||||
encryptedOutput1: '0xff',
|
||||
encryptedOutput2: '0xff',
|
||||
encryptedOutput1: packEncryptedMessage(
|
||||
outputs[0].keypair.encrypt({ blinding: outputs[0].blinding, amount: outputs[0].amount }),
|
||||
),
|
||||
encryptedOutput2: packEncryptedMessage(
|
||||
outputs[1].keypair.encrypt({ blinding: outputs[1].blinding, amount: outputs[1].amount }),
|
||||
),
|
||||
}
|
||||
|
||||
const extDataHash = getExtDataHash(extData)
|
||||
@ -104,16 +108,15 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
|
||||
}
|
||||
}
|
||||
|
||||
async function deposit({ tornadoPool }) {
|
||||
const amount = 1e6
|
||||
async function deposit({ tornadoPool, utxo }) {
|
||||
const inputs = [new Utxo(), new Utxo()]
|
||||
const outputs = [new Utxo({ amount }), new Utxo()]
|
||||
const outputs = [utxo, new Utxo()]
|
||||
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree({ tornadoPool }),
|
||||
extAmount: amount,
|
||||
extAmount: utxo.amount,
|
||||
fee: 0,
|
||||
recipient: 0,
|
||||
relayer: 0,
|
||||
@ -121,7 +124,7 @@ async function deposit({ tornadoPool }) {
|
||||
|
||||
console.log('Sending deposit transaction...')
|
||||
const receipt = await tornadoPool.transaction(proof, ...args, {
|
||||
value: amount,
|
||||
value: utxo.amount,
|
||||
gasLimit: 1e6,
|
||||
})
|
||||
console.log(`Receipt ${receipt.hash}`)
|
||||
@ -130,7 +133,7 @@ async function deposit({ tornadoPool }) {
|
||||
|
||||
async function merge({ tornadoPool }) {
|
||||
const amount = 1e6
|
||||
const inputs = new Array(16).fill(0).map(_ => new Utxo())
|
||||
const inputs = new Array(16).fill(0).map((_) => new Utxo())
|
||||
const outputs = [new Utxo({ amount }), new Utxo()]
|
||||
|
||||
const { proof, args } = await getProof({
|
||||
@ -143,7 +146,6 @@ async function merge({ tornadoPool }) {
|
||||
relayer: 0,
|
||||
})
|
||||
|
||||
console.log('Sending merge transaction...', proof, args)
|
||||
const receipt = await tornadoPool.transaction(proof, ...args, {
|
||||
value: amount,
|
||||
gasLimit: 1e6,
|
||||
@ -152,12 +154,9 @@ async function merge({ tornadoPool }) {
|
||||
return outputs[0]
|
||||
}
|
||||
|
||||
async function transact({ tornadoPool, utxo }) {
|
||||
const inputs = [utxo, new Utxo()]
|
||||
const outputs = [
|
||||
new Utxo({ amount: utxo.amount / 4 }),
|
||||
new Utxo({ amount: (utxo.amount * 3) / 4, keypair: utxo.keypair }),
|
||||
]
|
||||
async function transact({ tornadoPool, input, output }) {
|
||||
const inputs = [input, new Utxo()]
|
||||
const outputs = [output, new Utxo()]
|
||||
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
@ -175,15 +174,15 @@ async function transact({ tornadoPool, utxo }) {
|
||||
return outputs[0]
|
||||
}
|
||||
|
||||
async function withdraw({ tornadoPool, utxo, recipient }) {
|
||||
const inputs = [utxo, new Utxo()]
|
||||
const outputs = [new Utxo(), new Utxo()]
|
||||
async function withdraw({ tornadoPool, input, change, recipient }) {
|
||||
const inputs = [input, new Utxo()]
|
||||
const outputs = [change, new Utxo()]
|
||||
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree({ tornadoPool }),
|
||||
extAmount: FIELD_SIZE.sub(utxo.amount),
|
||||
extAmount: FIELD_SIZE.sub(input.amount.sub(change.amount)),
|
||||
fee: 0,
|
||||
recipient,
|
||||
relayer: 0,
|
||||
|
@ -2,18 +2,21 @@ const { encrypt, decrypt, getEncryptionPublicKey } = require('eth-sig-util')
|
||||
const { ethers } = require('hardhat')
|
||||
const { BigNumber } = ethers
|
||||
const { randomBN, poseidonHash, toFixedHex } = require('./utils')
|
||||
const BNjs = require('bn.js')
|
||||
|
||||
class Keypair {
|
||||
constructor(privkey = ethers.Wallet.createRandom().privateKey) {
|
||||
this.privkey = privkey
|
||||
console.log(privkey)
|
||||
this.pubkey = poseidonHash([this.privkey])
|
||||
this.encryptionKey = getEncryptionPublicKey(privkey.slice(2))
|
||||
console.log('enc key', this.encryptionKey)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return toFixedHex(this.pubkey) + toFixedHex(this.encryptionKey).slice(2)
|
||||
return toFixedHex(this.pubkey) + Buffer.from(this.encryptionKey, 'base64').toString('hex')
|
||||
}
|
||||
|
||||
address() {
|
||||
return this.toString()
|
||||
}
|
||||
|
||||
static fromString(str) {
|
||||
@ -26,24 +29,21 @@ class Keypair {
|
||||
return Object.assign(new Keypair(), {
|
||||
privkey: null,
|
||||
pubkey: BigNumber.from('0x' + str.slice(0, 64)),
|
||||
encryptionKey: BigNumber.from('0x' + str.slice(64, 128)),
|
||||
encryptionKey: Buffer.from(str.slice(64, 128), 'hex').toString('base64'),
|
||||
})
|
||||
}
|
||||
|
||||
encrypt({ blinding, amount }) {
|
||||
console.log(BigNumber.from(blinding).toHexString())
|
||||
const bytes = Buffer.concat([
|
||||
Buffer.from(BigNumber.from(blinding).toHexString(), 0, 31),
|
||||
Buffer.from(BigNumber.from(amount).toHexString(), 0, 31),
|
||||
new BNjs(blinding.toString()).toBuffer('be', 31),
|
||||
new BNjs(amount.toString()).toBuffer('be', 31),
|
||||
])
|
||||
console.log(bytes)
|
||||
return encrypt(this.encryptionKey, { data: bytes.toString('base64') }, 'x25519-xsalsa20-poly1305')
|
||||
}
|
||||
|
||||
decrypt(data) {
|
||||
const decryptedMessage = decrypt(data, this.privkey.slice(2))
|
||||
const buf = Buffer.from(decryptedMessage, 'base64')
|
||||
console.log(buf)
|
||||
return {
|
||||
blinding: BigNumber.from('0x' + buf.slice(0, 31).toString('hex')),
|
||||
amount: BigNumber.from('0x' + buf.slice(31, 62).toString('hex')),
|
||||
|
@ -1,8 +1,17 @@
|
||||
/* global ethers */
|
||||
const { expect, should } = require('chai')
|
||||
should()
|
||||
const { BigNumber } = ethers
|
||||
|
||||
const { poseidonHash2, toFixedHex, takeSnapshot, revertSnapshot } = require('../src/utils')
|
||||
const {
|
||||
poseidonHash2,
|
||||
toFixedHex,
|
||||
takeSnapshot,
|
||||
revertSnapshot,
|
||||
packEncryptedMessage,
|
||||
unpackEncryptedMessage,
|
||||
} = require('../src/utils')
|
||||
const Utxo = require('../src/utxo')
|
||||
|
||||
const MERKLE_TREE_HEIGHT = 5
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
@ -10,21 +19,6 @@ const MerkleTree = require('fixed-merkle-tree')
|
||||
const { deposit, transact, withdraw, merge } = require('../src/index')
|
||||
const Keypair = require('../src/keypair')
|
||||
|
||||
describe.only('Keypair', () => {
|
||||
it('should work', () => {
|
||||
const blinding = 3
|
||||
const amount = 5
|
||||
const keypair = new Keypair()
|
||||
|
||||
const cyphertext = keypair.encrypt({ blinding, amount})
|
||||
console.log(cyphertext)
|
||||
const result = keypair.decrypt(cyphertext)
|
||||
console.log(result, result.blinding.toString())
|
||||
expect(result.blinding).to.be.equal(blinding)
|
||||
expect(result.amount).to.be.equal(amount)
|
||||
})
|
||||
})
|
||||
|
||||
describe('TornadoPool', () => {
|
||||
let snapshotId, tornadoPool
|
||||
|
||||
@ -47,19 +41,89 @@ describe('TornadoPool', () => {
|
||||
snapshotId = await takeSnapshot()
|
||||
})
|
||||
|
||||
it('encryp -> pack -> unpack -> decrypt should work', () => {
|
||||
const blinding = 3
|
||||
const amount = 5
|
||||
const keypair = new Keypair()
|
||||
|
||||
const cyphertext = keypair.encrypt({ blinding, amount })
|
||||
|
||||
const packedMessage = packEncryptedMessage(cyphertext)
|
||||
|
||||
const unpackedMessage = unpackEncryptedMessage(packedMessage)
|
||||
|
||||
const result = keypair.decrypt(unpackedMessage)
|
||||
|
||||
expect(result.blinding).to.be.equal(blinding)
|
||||
expect(result.amount).to.be.equal(amount)
|
||||
})
|
||||
|
||||
it('should deposit, transact and withdraw', async function () {
|
||||
const utxo1 = await deposit({ tornadoPool })
|
||||
const utxo2 = await transact({ tornadoPool, utxo: utxo1 })
|
||||
/// deposit phase
|
||||
// Alice deposits into tornado pool
|
||||
const amount = BigNumber.from('10000000')
|
||||
const alicePrivateKey = ethers.Wallet.createRandom().privateKey // the private key we use for snarks and encryption, not for transactions
|
||||
const aliceKeypair = new Keypair(alicePrivateKey)
|
||||
|
||||
const depositInput = new Utxo({ amount, keypair: aliceKeypair })
|
||||
await deposit({ tornadoPool, utxo: depositInput })
|
||||
|
||||
// getting account data from chain to verify that Alice has an Input to spend now
|
||||
const filter = tornadoPool.filters.NewCommitment()
|
||||
let events = await tornadoPool.queryFilter(filter)
|
||||
let unpackedMessage = unpackEncryptedMessage(events[0].args.encryptedOutput)
|
||||
let decryptedMessage = aliceKeypair.decrypt(unpackedMessage)
|
||||
let aliceInputIndex = events[0].args.index
|
||||
expect(decryptedMessage.amount).to.be.equal(amount)
|
||||
expect(decryptedMessage.blinding).to.be.equal(depositInput.blinding)
|
||||
|
||||
/// transact phase.
|
||||
// Bob gives Alice address to send some eth inside pool
|
||||
const bobPrivateKey = ethers.Wallet.createRandom().privateKey
|
||||
const bobKeypair = new Keypair(bobPrivateKey)
|
||||
const bobAddress = bobKeypair.address()
|
||||
|
||||
// but alice does not have Bob's privkey so let's build keypair without it
|
||||
const bobKeypairForEncryption = Keypair.fromString(bobAddress)
|
||||
|
||||
// let's build input for the shielded transaction
|
||||
const aliceInput = new Utxo({
|
||||
amount,
|
||||
blinding: depositInput.blinding,
|
||||
index: aliceInputIndex,
|
||||
keypair: aliceKeypair,
|
||||
})
|
||||
const bobInput = new Utxo({ amount, keypair: bobKeypairForEncryption })
|
||||
|
||||
await transact({ tornadoPool, input: aliceInput, output: bobInput })
|
||||
|
||||
// getting account data from chain to verify that Bob has an Input to spend now
|
||||
const fromBlock = await ethers.provider.getBlock()
|
||||
events = await tornadoPool.queryFilter(filter, fromBlock.number)
|
||||
const bobInputIndex = events[0].args.index
|
||||
unpackedMessage = unpackEncryptedMessage(events[0].args.encryptedOutput)
|
||||
decryptedMessage = bobKeypair.decrypt(unpackedMessage)
|
||||
expect(decryptedMessage.amount).to.be.equal(amount)
|
||||
expect(decryptedMessage.blinding).to.be.equal(bobInput.blinding)
|
||||
|
||||
/// withdraw phase
|
||||
// now Bob wants to exit the pool using a half of its funds
|
||||
const bobInputForWithdraw = new Utxo({
|
||||
amount,
|
||||
blinding: bobInput.blinding,
|
||||
index: bobInputIndex,
|
||||
keypair: bobKeypair,
|
||||
})
|
||||
const bobChange = new Utxo({ amount: amount.div(2), keypair: bobKeypair })
|
||||
const recipient = '0xc2Ba33d4c0d2A92fb4f1a07C273c5d21E688Eb48'
|
||||
await withdraw({ tornadoPool, utxo: utxo2, recipient })
|
||||
await withdraw({ tornadoPool, input: bobInputForWithdraw, change: bobChange, recipient })
|
||||
|
||||
let bal = await ethers.provider.getBalance(recipient)
|
||||
const bal = await ethers.provider.getBalance(recipient)
|
||||
expect(bal).to.be.gt(0)
|
||||
})
|
||||
|
||||
it('should work with 16 inputs', async function () {
|
||||
const utxo1 = await merge({tornadoPool})
|
||||
const utxo1 = await merge({ tornadoPool })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
|
Loading…
Reference in New Issue
Block a user