mirror of
https://github.com/tornadocash/tornado-nova
synced 2024-02-02 14:53:56 +01:00
utxo class, separate private key for each input
This commit is contained in:
parent
238431233b
commit
bd2252afa3
@ -26,11 +26,10 @@ template Transaction(levels, zeroLeaf) {
|
||||
signal input fee;
|
||||
signal input extDataHash;
|
||||
|
||||
signal private input privateKey;
|
||||
|
||||
// data for 2 transaction inputs
|
||||
signal private input inAmount[2];
|
||||
signal private input inBlinding[2];
|
||||
signal private input inPrivateKey[2];
|
||||
signal private input inPathIndices[2];
|
||||
signal private input inPathElements[2][levels];
|
||||
|
||||
@ -42,6 +41,7 @@ template Transaction(levels, zeroLeaf) {
|
||||
signal private input outPathElements[levels - 1];
|
||||
|
||||
component inUtxoHasher[2];
|
||||
component inKeypair[2];
|
||||
component outUtxoHasher[2];
|
||||
component nullifierHasher[2];
|
||||
component checkRoot[2]
|
||||
@ -49,20 +49,20 @@ template Transaction(levels, zeroLeaf) {
|
||||
component inAmountCheck[2];
|
||||
component outAmountCheck[2];
|
||||
|
||||
component keypair = Keypair();
|
||||
keypair.privateKey <== privateKey;
|
||||
|
||||
// verify correctness of transaction inputs
|
||||
for (var tx = 0; tx < 2; tx++) {
|
||||
inKeypair[tx] = Keypair();
|
||||
inKeypair[tx].privateKey <== inPrivateKey[tx];
|
||||
|
||||
inUtxoHasher[tx] = TransactionHasher();
|
||||
inUtxoHasher[tx].amount <== inAmount[tx];
|
||||
inUtxoHasher[tx].blinding <== inBlinding[tx];
|
||||
inUtxoHasher[tx].publicKey <== keypair.publicKey;
|
||||
inUtxoHasher[tx].publicKey <== inKeypair[tx].publicKey;
|
||||
|
||||
nullifierHasher[tx] = NullifierHasher();
|
||||
nullifierHasher[tx].commitment <== inUtxoHasher[tx].commitment;
|
||||
nullifierHasher[tx].merklePath <== inPathIndices[tx];
|
||||
nullifierHasher[tx].privateKey <== keypair.privateKey;
|
||||
nullifierHasher[tx].privateKey <== inPrivateKey[tx];
|
||||
nullifierHasher[tx].nullifier === inputNullifier[tx];
|
||||
|
||||
tree[tx] = MerkleTree(levels);
|
||||
|
@ -29,8 +29,8 @@ contract TornadoPool is ReentrancyGuard {
|
||||
IVerifier public verifier;
|
||||
|
||||
struct ExtData {
|
||||
address payable _recipient;
|
||||
address payable _relayer;
|
||||
address payable recipient;
|
||||
address payable relayer;
|
||||
bytes encryptedOutput1;
|
||||
bytes encryptedOutput2;
|
||||
}
|
||||
@ -87,11 +87,11 @@ contract TornadoPool is ReentrancyGuard {
|
||||
require(msg.value == uint256(extAmount), "Incorrect amount of ETH sent on deposit");
|
||||
} else {
|
||||
require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal");
|
||||
_extData._recipient.transfer(uint256(-extAmount));
|
||||
_extData.recipient.transfer(uint256(-extAmount));
|
||||
}
|
||||
|
||||
if (_fee > 0) {
|
||||
_extData._relayer.transfer(_fee);
|
||||
_extData.relayer.transfer(_fee);
|
||||
}
|
||||
|
||||
// todo enforce currentCommitmentIndex value in snark
|
||||
|
330
src/index.js
330
src/index.js
@ -3,127 +3,88 @@ const MerkleTree = require('fixed-merkle-tree')
|
||||
const Web3 = require('web3')
|
||||
const { ethers } = require('hardhat')
|
||||
const { BigNumber } = ethers
|
||||
const { randomBN, bitsToNumber, toFixedHex, toBuffer, poseidonHash, poseidonHash2 } = require('./utils')
|
||||
const { toFixedHex, poseidonHash2, getExtDataHash, FIELD_SIZE } = require('./utils')
|
||||
const Utxo = require('./utxo')
|
||||
|
||||
let contract, web3
|
||||
const { prove } = require('./prover')
|
||||
const FIELD_SIZE = '21888242871839275222246405745257275088548364400416034343698204186575808495617'
|
||||
const MERKLE_TREE_HEIGHT = 5
|
||||
const RPC_URL = 'http://localhost:8545'
|
||||
|
||||
function fromPrivkey(privkey) {
|
||||
return {
|
||||
privkey,
|
||||
pubkey: poseidonHash([privkey]),
|
||||
}
|
||||
}
|
||||
|
||||
function randomKeypair() {
|
||||
return fromPrivkey(randomBN())
|
||||
}
|
||||
|
||||
function createZeroUtxo(keypair) {
|
||||
return createUtxo(
|
||||
0,
|
||||
randomBN(),
|
||||
keypair.pubkey,
|
||||
keypair.privkey,
|
||||
Array(MERKLE_TREE_HEIGHT).fill(0),
|
||||
Array(MERKLE_TREE_HEIGHT).fill(0),
|
||||
)
|
||||
}
|
||||
|
||||
function createOutput(amount, pubkey) {
|
||||
if (!pubkey) {
|
||||
throw new Error('no pubkey')
|
||||
}
|
||||
return createUtxo(amount, randomBN(), pubkey)
|
||||
}
|
||||
|
||||
function createInput({ amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements }) {
|
||||
return createUtxo(amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements)
|
||||
}
|
||||
|
||||
/// unsafe function without sanity checks
|
||||
function createUtxo(amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements) {
|
||||
let utxo = { amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements }
|
||||
utxo.commitment = poseidonHash([amount, blinding, pubkey])
|
||||
if (privkey) {
|
||||
utxo.nullifier = poseidonHash([utxo.commitment, bitsToNumber(merklePathIndices), privkey])
|
||||
}
|
||||
return utxo
|
||||
}
|
||||
|
||||
function createDeposit(amount, keypair) {
|
||||
const fakeKeypair = randomKeypair()
|
||||
const output = createOutput(amount, keypair.pubkey)
|
||||
output.privkey = keypair.privkey
|
||||
const tx = {
|
||||
inputs: [createZeroUtxo(fakeKeypair), createZeroUtxo(fakeKeypair)],
|
||||
outputs: [output, createZeroUtxo(fakeKeypair)], // todo shuffle
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function buildMerkleTree() {
|
||||
console.log('Getting contract state...')
|
||||
const events = await contract.getPastEvents('NewCommitment', { fromBlock: 0, toBlock: 'latest' })
|
||||
const leaves = events
|
||||
.sort((a, b) => a.returnValues.index - b.returnValues.index) // todo sort by event date
|
||||
.map((e) => toFixedHex(e.returnValues.commitment))
|
||||
console.log('leaves', leaves)
|
||||
// console.log('leaves', leaves)
|
||||
return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 })
|
||||
}
|
||||
|
||||
async function insertOutput(tree, output) {
|
||||
await tree.insert(output.commitment)
|
||||
let { pathElements, pathIndices } = await tree.path(tree.elements().length - 1)
|
||||
output.merklePathIndices = pathIndices
|
||||
output.merklePathElements = pathElements
|
||||
}
|
||||
|
||||
async function getProof({ input1, input2, output1, output2, tree, extAmount, fee, recipient, relayer }) {
|
||||
const oldRoot = tree.root()
|
||||
|
||||
// if deposit require(extAmount > 0)
|
||||
|
||||
if (input1.amount !== 0) {
|
||||
// transact
|
||||
const index1 = await tree.indexOf(toFixedHex(input1.commitment))
|
||||
const path1 = await tree.path(index1)
|
||||
async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer }) {
|
||||
// todo shuffle inputs and outputs
|
||||
if (inputs.length !== 2 || outputs.length !== 2 ) {
|
||||
throw new Error('Unsupported number of inputs/outputs')
|
||||
}
|
||||
|
||||
await insertOutput(tree, output1)
|
||||
await insertOutput(tree, output2)
|
||||
let inputMerklePathIndices = []
|
||||
let inputMerklePathElements = []
|
||||
|
||||
extData = recipient + relayer // TODO
|
||||
for (const input of inputs) {
|
||||
if (input.amount > 0) {
|
||||
const index = tree.indexOf(toFixedHex(input.getCommitment()))
|
||||
if (index < 0) {
|
||||
throw new Error(`Input commitment ${input.getCommitment()} was not found`)
|
||||
}
|
||||
inputMerklePathIndices.push(index)
|
||||
inputMerklePathElements.push(tree.path(index).pathElements)
|
||||
} else {
|
||||
inputMerklePathIndices.push(0)
|
||||
inputMerklePathElements.push(new Array(tree.levels).fill(0))
|
||||
}
|
||||
}
|
||||
|
||||
const oldRoot = tree.root()
|
||||
for (const output of outputs) {
|
||||
output.index = tree.elements().length
|
||||
tree.insert(output.getCommitment())
|
||||
}
|
||||
const outputIndex = tree.elements().length - 1
|
||||
const outputPath = tree.path(outputIndex).pathElements.slice(1)
|
||||
|
||||
const extData = {
|
||||
recipient: toFixedHex(recipient, 20),
|
||||
relayer: toFixedHex(relayer, 20),
|
||||
encryptedOutput1: '0xff',
|
||||
encryptedOutput2: '0xff',
|
||||
}
|
||||
|
||||
const extDataHash = getExtDataHash(extData)
|
||||
let input = {
|
||||
root: oldRoot,
|
||||
newRoot: tree.root(),
|
||||
inputNullifier: [input1.nullifier, input2.nullifier],
|
||||
outputCommitment: [outputs1.commitment, outputs2.commitment],
|
||||
inputNullifier: inputs.map(x => x.getNullifier()),
|
||||
outputCommitment: outputs.map(x => x.getCommitment()),
|
||||
extAmount,
|
||||
fee,
|
||||
extData,
|
||||
|
||||
// private inputs
|
||||
privateKey: inputs1.privkey, // TODO make sure you use the right one when you shuffle inputs
|
||||
extDataHash,
|
||||
|
||||
// data for 2 transaction inputs
|
||||
inAmount: [inputs1.amount, inputs2.amount],
|
||||
inBlinding: [inputs1.blinding, inputs2.blinding],
|
||||
inPathIndices: [bitsToNumber(inputs1.merklePathIndices), bitsToNumber(inputs2.merklePathIndices)],
|
||||
inPathElements: [inputs1.merklePathElements, inputs2.merklePathElements],
|
||||
inAmount: inputs.map(x => x.amount),
|
||||
inPrivateKey: inputs.map(x => x.privkey),
|
||||
inBlinding: inputs.map(x => x.blinding),
|
||||
inPathIndices: inputMerklePathIndices,
|
||||
inPathElements: inputMerklePathElements,
|
||||
|
||||
// data for 2 transaction outputs
|
||||
outAmount: [outputs1.amount, outputs2.amount],
|
||||
outBlinding: [outputs1.blinding, outputs2.blinding],
|
||||
outPubkey: [outputs1.pubkey, outputs2.pubkey],
|
||||
outPathIndices: bitsToNumber[outputs1.merklePathIndices.slice(1)],
|
||||
outPathElements: [outputs1.merklePathElements.slice(1)],
|
||||
outAmount: outputs.map(x => x.amount),
|
||||
outBlinding: outputs.map(x => x.blinding),
|
||||
outPubkey: outputs.map(x => x.pubkey),
|
||||
outPathIndices: outputIndex >> 1,
|
||||
outPathElements: outputPath,
|
||||
}
|
||||
|
||||
console.log('SNARK input', input)
|
||||
//console.log('SNARK input', input)
|
||||
|
||||
console.log('Generating SNARK proof...')
|
||||
const proof = await prove(input, './artifacts/circuits/transaction')
|
||||
@ -131,12 +92,14 @@ async function getProof({ input1, input2, output1, output2, tree, extAmount, fee
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
toFixedHex(input.newRoot),
|
||||
[toFixedHex(input1.nullifier), toFixedHex(input2.nullifier)],
|
||||
[toFixedHex(output1.commitment), toFixedHex(output2.commitment)],
|
||||
toFixedHex(0),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(extData), // extData hash actually
|
||||
inputs.map(x => toFixedHex(x.getNullifier())),
|
||||
outputs.map(x => toFixedHex(x.getCommitment())),
|
||||
toFixedHex(extAmount),
|
||||
toFixedHex(fee),
|
||||
extData,
|
||||
toFixedHex(extDataHash),
|
||||
]
|
||||
// console.log('Solidity args', args)
|
||||
|
||||
return {
|
||||
proof,
|
||||
@ -146,16 +109,13 @@ async function getProof({ input1, input2, output1, output2, tree, extAmount, fee
|
||||
|
||||
async function deposit() {
|
||||
const amount = 1e6
|
||||
const tree = await buildMerkleTree()
|
||||
const keypair = randomKeypair()
|
||||
const tx = createDeposit(amount, keypair)
|
||||
const inputs = [new Utxo(), new Utxo()]
|
||||
const outputs = [new Utxo({ amount }), new Utxo()]
|
||||
|
||||
const { proof, args } = await getProof({
|
||||
input1: tx.inputs[0],
|
||||
input2: tx.inputs[1],
|
||||
output1: tx.outputs[1],
|
||||
output2: tx.outputs[1],
|
||||
tree,
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree(),
|
||||
extAmount: amount,
|
||||
fee: 0,
|
||||
recipient: 0,
|
||||
@ -167,155 +127,47 @@ async function deposit() {
|
||||
.transaction(proof, ...args)
|
||||
.send({ value: amount, from: web3.eth.defaultAccount, gas: 1e6 })
|
||||
console.log(`Receipt ${receipt.transactionHash}`)
|
||||
return tx.outputs[0]
|
||||
return outputs[0]
|
||||
}
|
||||
|
||||
async function transact(txOutput) {
|
||||
console.log('txOutput', txOutput)
|
||||
const tree = await buildMerkleTree()
|
||||
console.log('tree', tree)
|
||||
const oldRoot = await tree.root()
|
||||
const keypair = randomKeypair()
|
||||
async function transact(utxo) {
|
||||
const inputs = [utxo, new Utxo()]
|
||||
const outputs = [
|
||||
new Utxo({ amount: utxo.amount / 4 }),
|
||||
new Utxo({ amount: utxo.amount * 3 / 4, privkey: utxo.privkey}),
|
||||
]
|
||||
|
||||
const index = await tree.indexOf(toFixedHex(txOutput.commitment))
|
||||
console.log('index', index)
|
||||
const { pathElements, pathIndices } = await tree.path(index)
|
||||
console.log('pathIndices', pathIndices)
|
||||
txOutput.merklePathElements = pathElements
|
||||
const input1 = createInput(txOutput)
|
||||
const tx = {
|
||||
inputs: [input1, createZeroUtxo(fromPrivkey(txOutput.privkey))],
|
||||
outputs: [
|
||||
createOutput(txOutput.amount / 4, keypair.pubkey),
|
||||
createOutput((txOutput.amount * 3) / 4, txOutput.pubkey),
|
||||
], // todo shuffle
|
||||
}
|
||||
tx.outputs[0].privkey = keypair.privkey
|
||||
tx.outputs[1].privkey = txOutput.privkey
|
||||
await insertOutput(tree, tx.outputs[0])
|
||||
await insertOutput(tree, tx.outputs[1])
|
||||
console.log('Note', tx.outputs[0])
|
||||
|
||||
let input = {
|
||||
root: oldRoot,
|
||||
newRoot: await tree.root(),
|
||||
inputNullifier: [tx.inputs[0].nullifier, tx.inputs[1].nullifier],
|
||||
outputCommitment: [tx.outputs[0].commitment, tx.outputs[1].commitment],
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree(),
|
||||
extAmount: 0,
|
||||
fee: 0,
|
||||
recipient: 0,
|
||||
relayer: 0,
|
||||
|
||||
// private inputs
|
||||
privateKey: tx.inputs[0].privkey,
|
||||
|
||||
// data for 2 transaction inputs
|
||||
inAmount: [tx.inputs[0].amount, tx.inputs[1].amount],
|
||||
inBlinding: [tx.inputs[0].blinding, tx.inputs[1].blinding],
|
||||
inPathIndices: [
|
||||
bitsToNumber(tx.inputs[0].merklePathIndices),
|
||||
bitsToNumber(tx.inputs[1].merklePathIndices),
|
||||
],
|
||||
inPathElements: [tx.inputs[0].merklePathElements, tx.inputs[1].merklePathElements],
|
||||
|
||||
// data for 2 transaction outputs
|
||||
outAmount: [tx.outputs[0].amount, tx.outputs[1].amount],
|
||||
outBlinding: [tx.outputs[0].blinding, tx.outputs[1].blinding],
|
||||
outPubkey: [tx.outputs[0].pubkey, tx.outputs[1].pubkey],
|
||||
outPathIndices: bitsToNumber(tx.outputs[0].merklePathIndices.slice(1)),
|
||||
outPathElements: tx.outputs[0].merklePathElements.slice(1),
|
||||
}
|
||||
|
||||
console.log('TRANSFER input', input)
|
||||
|
||||
console.log('Generating SNARK proof...')
|
||||
const proof = await prove(input, './artifacts/circuits/transaction')
|
||||
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
toFixedHex(input.newRoot),
|
||||
[toFixedHex(tx.inputs[0].nullifier), toFixedHex(tx.inputs[1].nullifier)],
|
||||
[toFixedHex(tx.outputs[0].commitment), toFixedHex(tx.outputs[1].commitment)],
|
||||
toFixedHex(0),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
]
|
||||
})
|
||||
|
||||
console.log('Sending transfer transaction...')
|
||||
const receipt = await contract.methods
|
||||
.transaction(proof, ...args)
|
||||
.send({ from: web3.eth.defaultAccount, gas: 1e6 })
|
||||
console.log(`Receipt ${receipt.transactionHash}`)
|
||||
return tx.outputs[0]
|
||||
return outputs[0]
|
||||
}
|
||||
|
||||
async function withdraw(txOutput) {
|
||||
console.log('txOutput', txOutput)
|
||||
const tree = await buildMerkleTree()
|
||||
const oldRoot = await tree.root()
|
||||
async function withdraw(utxo) {
|
||||
const inputs = [utxo, new Utxo()]
|
||||
const outputs = [new Utxo(), new Utxo()]
|
||||
|
||||
const index = await tree.indexOf(toFixedHex(txOutput.commitment))
|
||||
console.log('index', index)
|
||||
const { pathElements, pathIndices } = await tree.path(index)
|
||||
console.log('pathIndices', pathIndices)
|
||||
txOutput.merklePathElements = pathElements
|
||||
const input1 = createInput(txOutput)
|
||||
const fakeKeypair = randomKeypair()
|
||||
const tx = {
|
||||
inputs: [input1, createZeroUtxo(fromPrivkey(txOutput.privkey))],
|
||||
outputs: [createZeroUtxo(fakeKeypair), createZeroUtxo(fakeKeypair)], // todo shuffle
|
||||
}
|
||||
await insertOutput(tree, tx.outputs[0])
|
||||
await insertOutput(tree, tx.outputs[1])
|
||||
|
||||
let input = {
|
||||
root: oldRoot,
|
||||
newRoot: await tree.root(),
|
||||
inputNullifier: [tx.inputs[0].nullifier, tx.inputs[1].nullifier],
|
||||
outputCommitment: [tx.outputs[0].commitment, tx.outputs[1].commitment],
|
||||
extAmount: BigNumber.from(FIELD_SIZE).sub(BigNumber.from(txOutput.amount)),
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree(),
|
||||
extAmount: FIELD_SIZE.sub(utxo.amount),
|
||||
fee: 0,
|
||||
recipient: '0xc2Ba33d4c0d2A92fb4f1a07C273c5d21E688Eb48',
|
||||
relayer: 0,
|
||||
|
||||
// private inputs
|
||||
privateKey: tx.inputs[0].privkey,
|
||||
|
||||
// data for 2 transaction inputs
|
||||
inAmount: [tx.inputs[0].amount, tx.inputs[1].amount],
|
||||
inBlinding: [tx.inputs[0].blinding, tx.inputs[1].blinding],
|
||||
inPathIndices: [
|
||||
bitsToNumber(tx.inputs[0].merklePathIndices),
|
||||
bitsToNumber(tx.inputs[1].merklePathIndices),
|
||||
],
|
||||
inPathElements: [tx.inputs[0].merklePathElements, tx.inputs[1].merklePathElements],
|
||||
|
||||
// data for 2 transaction outputs
|
||||
outAmount: [tx.outputs[0].amount, tx.outputs[1].amount],
|
||||
outBlinding: [tx.outputs[0].blinding, tx.outputs[1].blinding],
|
||||
outPubkey: [tx.outputs[0].pubkey, tx.outputs[1].pubkey],
|
||||
outPathIndices: bitsToNumber(tx.outputs[0].merklePathIndices.slice(1)),
|
||||
outPathElements: tx.outputs[0].merklePathElements.slice(1),
|
||||
}
|
||||
|
||||
console.log('WITHDRAW input', input)
|
||||
|
||||
console.log('Generating SNARK proof...')
|
||||
const proof = await prove(input, './artifacts/circuits/transaction')
|
||||
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
toFixedHex(input.newRoot),
|
||||
[toFixedHex(tx.inputs[0].nullifier), toFixedHex(tx.inputs[1].nullifier)],
|
||||
[toFixedHex(tx.outputs[0].commitment), toFixedHex(tx.outputs[1].commitment)],
|
||||
toFixedHex(input.extAmount),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
]
|
||||
|
||||
console.log('args', args)
|
||||
})
|
||||
|
||||
console.log('Sending withdraw transaction...')
|
||||
const receipt = await contract.methods
|
||||
@ -333,12 +185,12 @@ async function main() {
|
||||
})
|
||||
netId = await web3.eth.net.getId()
|
||||
const contractData = require('../artifacts/contracts/TornadoPool.sol/TornadoPool.json')
|
||||
contract = new web3.eth.Contract(contractData.abi, '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9')
|
||||
contract = new web3.eth.Contract(contractData.abi, '0x0E801D84Fa97b50751Dbf25036d067dCf18858bF')
|
||||
web3.eth.defaultAccount = (await web3.eth.getAccounts())[0]
|
||||
|
||||
const txOutput = await deposit()
|
||||
const txOutput1 = await transact(txOutput)
|
||||
await withdraw(txOutput1)
|
||||
const utxo1 = await deposit()
|
||||
const utxo2 = await transact(utxo1)
|
||||
await withdraw(utxo2)
|
||||
}
|
||||
|
||||
main()
|
||||
|
@ -8,7 +8,7 @@ const exec = util.promisify(require('child_process').exec)
|
||||
|
||||
function prove(input, keyBasePath) {
|
||||
input = utils.stringifyBigInts(input)
|
||||
console.log('input', input)
|
||||
// console.log('input', input)
|
||||
return tmp.dir().then(async (dir) => {
|
||||
dir = dir.path
|
||||
let out
|
||||
@ -28,6 +28,8 @@ function prove(input, keyBasePath) {
|
||||
out = await exec(
|
||||
`zkutil prove -c ${keyBasePath}.r1cs -p ${keyBasePath}.params -w ${dir}/witness.json -r ${dir}/proof.json -o ${dir}/public.json`,
|
||||
)
|
||||
// todo catch inconsistent input during witness generation
|
||||
await exec(`zkutil verify -p ${keyBasePath}.params -r ${dir}/proof.json -i ${dir}/public.json`)
|
||||
} catch (e) {
|
||||
console.log(out, e)
|
||||
throw e
|
||||
|
29
src/utils.js
29
src/utils.js
@ -1,7 +1,7 @@
|
||||
const crypto = require('crypto')
|
||||
const ethers = require('ethers')
|
||||
const BigNumber = ethers.BigNumber
|
||||
const { poseidon } = require('circomlib')
|
||||
const {poseidon} = require('circomlib')
|
||||
|
||||
const poseidonHash = (items) => BigNumber.from(poseidon(items).toString())
|
||||
const poseidonHash2 = (a, b) => poseidonHash([a, b])
|
||||
@ -12,13 +12,19 @@ const FIELD_SIZE = BigNumber.from(
|
||||
/** Generate random number of specified byte length */
|
||||
const randomBN = (nbytes = 31) => BigNumber.from(crypto.randomBytes(nbytes))
|
||||
|
||||
function getExtDataHash({ recipient, relayer, encryptedOutput1, encryptedOutput2 }) {
|
||||
function getExtDataHash({recipient, relayer, encryptedOutput1, encryptedOutput2}) {
|
||||
const abi = new ethers.utils.AbiCoder()
|
||||
|
||||
const encodedData = abi.encode(
|
||||
['address', 'address', 'bytes', 'bytes'],
|
||||
[toFixedHex(recipient, 20), toFixedHex(relayer, 20), encryptedOutput1, encryptedOutput2],
|
||||
['tuple(address recipient,address relayer,bytes encryptedOutput1,bytes encryptedOutput2)'],
|
||||
[{
|
||||
recipient: toFixedHex(recipient, 20),
|
||||
relayer: toFixedHex(relayer, 20),
|
||||
encryptedOutput1: encryptedOutput1,
|
||||
encryptedOutput2: encryptedOutput2,
|
||||
}],
|
||||
)
|
||||
const hash = ethers.utils.keccak256(encodedData)
|
||||
|
||||
return BigNumber.from(hash).mod(FIELD_SIZE)
|
||||
}
|
||||
|
||||
@ -26,8 +32,8 @@ function getExtDataHash({ recipient, relayer, encryptedOutput1, encryptedOutput2
|
||||
const toFixedHex = (number, length = 32) =>
|
||||
'0x' +
|
||||
(number instanceof Buffer
|
||||
? number.toString('hex')
|
||||
: BigNumber.from(number).toHexString().slice(2)
|
||||
? number.toString('hex')
|
||||
: BigNumber.from(number).toHexString().slice(2)
|
||||
).padStart(length * 2, '0')
|
||||
|
||||
const toBuffer = (value, length) =>
|
||||
@ -39,18 +45,9 @@ const toBuffer = (value, length) =>
|
||||
'hex',
|
||||
)
|
||||
|
||||
function bitsToNumber(bits) {
|
||||
let result = 0
|
||||
for (const item of bits.slice().reverse()) {
|
||||
result = (result << 1) + item
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FIELD_SIZE,
|
||||
randomBN,
|
||||
bitsToNumber,
|
||||
toFixedHex,
|
||||
toBuffer,
|
||||
poseidonHash,
|
||||
|
46
src/utxo.js
Normal file
46
src/utxo.js
Normal file
@ -0,0 +1,46 @@
|
||||
const { ethers } = require('hardhat')
|
||||
const { BigNumber } = ethers
|
||||
const { randomBN, poseidonHash } = require('./utils')
|
||||
|
||||
function fromPrivkey(privkey) {
|
||||
return {
|
||||
privkey,
|
||||
pubkey: poseidonHash([privkey]),
|
||||
}
|
||||
}
|
||||
|
||||
class Utxo {
|
||||
constructor({amount, pubkey, privkey, blinding, index} = {}) {
|
||||
if (!pubkey) {
|
||||
if (privkey) {
|
||||
pubkey = fromPrivkey(privkey).pubkey
|
||||
} else {
|
||||
({pubkey, privkey} = fromPrivkey(randomBN()))
|
||||
}
|
||||
}
|
||||
this.amount = BigNumber.from(amount || 0);
|
||||
this.blinding = blinding || randomBN();
|
||||
this.pubkey = pubkey;
|
||||
this.privkey = privkey;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
getCommitment() {
|
||||
if (!this._commitment) {
|
||||
this._commitment = poseidonHash([this.amount, this.blinding, this.pubkey])
|
||||
}
|
||||
return this._commitment
|
||||
}
|
||||
|
||||
getNullifier() {
|
||||
if (!this._nullifier) {
|
||||
if (this.amount > 0 && (!this.index || !this.privkey)) {
|
||||
throw new Error('Can not compute nullifier without utxo index or private key')
|
||||
}
|
||||
this._nullifier = poseidonHash([this.getCommitment(), this.index || 0, this.privkey || 0])
|
||||
}
|
||||
return this._nullifier
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Utxo
|
Loading…
Reference in New Issue
Block a user