encrypion and full test

This commit is contained in:
Alexey 2021-06-15 23:02:59 +03:00
parent 5bdc8d7871
commit 18571118d7
4 changed files with 119 additions and 51 deletions

5
TODO
View File

@ -7,3 +7,8 @@
* design * design
* race condition ? (sequencer or something) * race condition ? (sequencer or something)
* the current trusted setup is not secure * 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)

View File

@ -1,7 +1,7 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
const MerkleTree = require('fixed-merkle-tree') const MerkleTree = require('fixed-merkle-tree')
const { ethers } = require('hardhat') 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 Utxo = require('./utxo')
const { prove } = require('./prover') const { prove } = require('./prover')
@ -29,7 +29,7 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
if (input.amount > 0) { if (input.amount > 0) {
const index = tree.indexOf(toFixedHex(input.getCommitment())) const index = tree.indexOf(toFixedHex(input.getCommitment()))
if (index < 0) { 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) inputMerklePathIndices.push(index)
inputMerklePathElements.push(tree.path(index).pathElements) inputMerklePathElements.push(tree.path(index).pathElements)
@ -52,8 +52,12 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
const extData = { const extData = {
recipient: toFixedHex(recipient, 20), recipient: toFixedHex(recipient, 20),
relayer: toFixedHex(relayer, 20), relayer: toFixedHex(relayer, 20),
encryptedOutput1: '0xff', encryptedOutput1: packEncryptedMessage(
encryptedOutput2: '0xff', 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) const extDataHash = getExtDataHash(extData)
@ -104,16 +108,15 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
} }
} }
async function deposit({ tornadoPool }) { async function deposit({ tornadoPool, utxo }) {
const amount = 1e6
const inputs = [new Utxo(), new Utxo()] const inputs = [new Utxo(), new Utxo()]
const outputs = [new Utxo({ amount }), new Utxo()] const outputs = [utxo, new Utxo()]
const { proof, args } = await getProof({ const { proof, args } = await getProof({
inputs, inputs,
outputs, outputs,
tree: await buildMerkleTree({ tornadoPool }), tree: await buildMerkleTree({ tornadoPool }),
extAmount: amount, extAmount: utxo.amount,
fee: 0, fee: 0,
recipient: 0, recipient: 0,
relayer: 0, relayer: 0,
@ -121,7 +124,7 @@ async function deposit({ tornadoPool }) {
console.log('Sending deposit transaction...') console.log('Sending deposit transaction...')
const receipt = await tornadoPool.transaction(proof, ...args, { const receipt = await tornadoPool.transaction(proof, ...args, {
value: amount, value: utxo.amount,
gasLimit: 1e6, gasLimit: 1e6,
}) })
console.log(`Receipt ${receipt.hash}`) console.log(`Receipt ${receipt.hash}`)
@ -130,7 +133,7 @@ async function deposit({ tornadoPool }) {
async function merge({ tornadoPool }) { async function merge({ tornadoPool }) {
const amount = 1e6 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 outputs = [new Utxo({ amount }), new Utxo()]
const { proof, args } = await getProof({ const { proof, args } = await getProof({
@ -143,7 +146,6 @@ async function merge({ tornadoPool }) {
relayer: 0, relayer: 0,
}) })
console.log('Sending merge transaction...', proof, args)
const receipt = await tornadoPool.transaction(proof, ...args, { const receipt = await tornadoPool.transaction(proof, ...args, {
value: amount, value: amount,
gasLimit: 1e6, gasLimit: 1e6,
@ -152,12 +154,9 @@ async function merge({ tornadoPool }) {
return outputs[0] return outputs[0]
} }
async function transact({ tornadoPool, utxo }) { async function transact({ tornadoPool, input, output }) {
const inputs = [utxo, new Utxo()] const inputs = [input, new Utxo()]
const outputs = [ const outputs = [output, new Utxo()]
new Utxo({ amount: utxo.amount / 4 }),
new Utxo({ amount: (utxo.amount * 3) / 4, keypair: utxo.keypair }),
]
const { proof, args } = await getProof({ const { proof, args } = await getProof({
inputs, inputs,
@ -175,15 +174,15 @@ async function transact({ tornadoPool, utxo }) {
return outputs[0] return outputs[0]
} }
async function withdraw({ tornadoPool, utxo, recipient }) { async function withdraw({ tornadoPool, input, change, recipient }) {
const inputs = [utxo, new Utxo()] const inputs = [input, new Utxo()]
const outputs = [new Utxo(), new Utxo()] const outputs = [change, new Utxo()]
const { proof, args } = await getProof({ const { proof, args } = await getProof({
inputs, inputs,
outputs, outputs,
tree: await buildMerkleTree({ tornadoPool }), tree: await buildMerkleTree({ tornadoPool }),
extAmount: FIELD_SIZE.sub(utxo.amount), extAmount: FIELD_SIZE.sub(input.amount.sub(change.amount)),
fee: 0, fee: 0,
recipient, recipient,
relayer: 0, relayer: 0,

View File

@ -2,18 +2,21 @@ const { encrypt, decrypt, getEncryptionPublicKey } = require('eth-sig-util')
const { ethers } = require('hardhat') const { ethers } = require('hardhat')
const { BigNumber } = ethers const { BigNumber } = ethers
const { randomBN, poseidonHash, toFixedHex } = require('./utils') const { randomBN, poseidonHash, toFixedHex } = require('./utils')
const BNjs = require('bn.js')
class Keypair { class Keypair {
constructor(privkey = ethers.Wallet.createRandom().privateKey) { constructor(privkey = ethers.Wallet.createRandom().privateKey) {
this.privkey = privkey this.privkey = privkey
console.log(privkey)
this.pubkey = poseidonHash([this.privkey]) this.pubkey = poseidonHash([this.privkey])
this.encryptionKey = getEncryptionPublicKey(privkey.slice(2)) this.encryptionKey = getEncryptionPublicKey(privkey.slice(2))
console.log('enc key', this.encryptionKey)
} }
toString() { 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) { static fromString(str) {
@ -26,24 +29,21 @@ class Keypair {
return Object.assign(new Keypair(), { return Object.assign(new Keypair(), {
privkey: null, privkey: null,
pubkey: BigNumber.from('0x' + str.slice(0, 64)), 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 }) { encrypt({ blinding, amount }) {
console.log(BigNumber.from(blinding).toHexString())
const bytes = Buffer.concat([ const bytes = Buffer.concat([
Buffer.from(BigNumber.from(blinding).toHexString(), 0, 31), new BNjs(blinding.toString()).toBuffer('be', 31),
Buffer.from(BigNumber.from(amount).toHexString(), 0, 31), new BNjs(amount.toString()).toBuffer('be', 31),
]) ])
console.log(bytes)
return encrypt(this.encryptionKey, { data: bytes.toString('base64') }, 'x25519-xsalsa20-poly1305') return encrypt(this.encryptionKey, { data: bytes.toString('base64') }, 'x25519-xsalsa20-poly1305')
} }
decrypt(data) { decrypt(data) {
const decryptedMessage = decrypt(data, this.privkey.slice(2)) const decryptedMessage = decrypt(data, this.privkey.slice(2))
const buf = Buffer.from(decryptedMessage, 'base64') const buf = Buffer.from(decryptedMessage, 'base64')
console.log(buf)
return { return {
blinding: BigNumber.from('0x' + buf.slice(0, 31).toString('hex')), blinding: BigNumber.from('0x' + buf.slice(0, 31).toString('hex')),
amount: BigNumber.from('0x' + buf.slice(31, 62).toString('hex')), amount: BigNumber.from('0x' + buf.slice(31, 62).toString('hex')),

View File

@ -1,8 +1,17 @@
/* global ethers */ /* global ethers */
const { expect, should } = require('chai') const { expect, should } = require('chai')
should() 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 MERKLE_TREE_HEIGHT = 5
const MerkleTree = require('fixed-merkle-tree') 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 { deposit, transact, withdraw, merge } = require('../src/index')
const Keypair = require('../src/keypair') 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', () => { describe('TornadoPool', () => {
let snapshotId, tornadoPool let snapshotId, tornadoPool
@ -47,19 +41,89 @@ describe('TornadoPool', () => {
snapshotId = await takeSnapshot() 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 () { it('should deposit, transact and withdraw', async function () {
const utxo1 = await deposit({ tornadoPool }) /// deposit phase
const utxo2 = await transact({ tornadoPool, utxo: utxo1 }) // 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' 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) expect(bal).to.be.gt(0)
}) })
it('should work with 16 inputs', async function () { it('should work with 16 inputs', async function () {
const utxo1 = await merge({tornadoPool}) const utxo1 = await merge({ tornadoPool })
}) })
afterEach(async () => { afterEach(async () => {