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
* 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)

View File

@ -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,

View File

@ -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')),

View File

@ -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 () => {