utxo class, separate private key for each input

This commit is contained in:
poma 2021-06-08 21:50:34 +03:00
parent 238431233b
commit bd2252afa3
No known key found for this signature in database
GPG Key ID: BA20CB01FE165657
6 changed files with 164 additions and 267 deletions

View File

@ -26,11 +26,10 @@ template Transaction(levels, zeroLeaf) {
signal input fee; signal input fee;
signal input extDataHash; signal input extDataHash;
signal private input privateKey;
// data for 2 transaction inputs // data for 2 transaction inputs
signal private input inAmount[2]; signal private input inAmount[2];
signal private input inBlinding[2]; signal private input inBlinding[2];
signal private input inPrivateKey[2];
signal private input inPathIndices[2]; signal private input inPathIndices[2];
signal private input inPathElements[2][levels]; signal private input inPathElements[2][levels];
@ -42,6 +41,7 @@ template Transaction(levels, zeroLeaf) {
signal private input outPathElements[levels - 1]; signal private input outPathElements[levels - 1];
component inUtxoHasher[2]; component inUtxoHasher[2];
component inKeypair[2];
component outUtxoHasher[2]; component outUtxoHasher[2];
component nullifierHasher[2]; component nullifierHasher[2];
component checkRoot[2] component checkRoot[2]
@ -49,20 +49,20 @@ template Transaction(levels, zeroLeaf) {
component inAmountCheck[2]; component inAmountCheck[2];
component outAmountCheck[2]; component outAmountCheck[2];
component keypair = Keypair();
keypair.privateKey <== privateKey;
// verify correctness of transaction inputs // verify correctness of transaction inputs
for (var tx = 0; tx < 2; tx++) { for (var tx = 0; tx < 2; tx++) {
inKeypair[tx] = Keypair();
inKeypair[tx].privateKey <== inPrivateKey[tx];
inUtxoHasher[tx] = TransactionHasher(); inUtxoHasher[tx] = TransactionHasher();
inUtxoHasher[tx].amount <== inAmount[tx]; inUtxoHasher[tx].amount <== inAmount[tx];
inUtxoHasher[tx].blinding <== inBlinding[tx]; inUtxoHasher[tx].blinding <== inBlinding[tx];
inUtxoHasher[tx].publicKey <== keypair.publicKey; inUtxoHasher[tx].publicKey <== inKeypair[tx].publicKey;
nullifierHasher[tx] = NullifierHasher(); nullifierHasher[tx] = NullifierHasher();
nullifierHasher[tx].commitment <== inUtxoHasher[tx].commitment; nullifierHasher[tx].commitment <== inUtxoHasher[tx].commitment;
nullifierHasher[tx].merklePath <== inPathIndices[tx]; nullifierHasher[tx].merklePath <== inPathIndices[tx];
nullifierHasher[tx].privateKey <== keypair.privateKey; nullifierHasher[tx].privateKey <== inPrivateKey[tx];
nullifierHasher[tx].nullifier === inputNullifier[tx]; nullifierHasher[tx].nullifier === inputNullifier[tx];
tree[tx] = MerkleTree(levels); tree[tx] = MerkleTree(levels);

View File

@ -29,8 +29,8 @@ contract TornadoPool is ReentrancyGuard {
IVerifier public verifier; IVerifier public verifier;
struct ExtData { struct ExtData {
address payable _recipient; address payable recipient;
address payable _relayer; address payable relayer;
bytes encryptedOutput1; bytes encryptedOutput1;
bytes encryptedOutput2; bytes encryptedOutput2;
} }
@ -87,11 +87,11 @@ contract TornadoPool is ReentrancyGuard {
require(msg.value == uint256(extAmount), "Incorrect amount of ETH sent on deposit"); require(msg.value == uint256(extAmount), "Incorrect amount of ETH sent on deposit");
} else { } else {
require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal"); 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) { if (_fee > 0) {
_extData._relayer.transfer(_fee); _extData.relayer.transfer(_fee);
} }
// todo enforce currentCommitmentIndex value in snark // todo enforce currentCommitmentIndex value in snark

View File

@ -3,127 +3,88 @@ const MerkleTree = require('fixed-merkle-tree')
const Web3 = require('web3') const Web3 = require('web3')
const { ethers } = require('hardhat') const { ethers } = require('hardhat')
const { BigNumber } = ethers 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 let contract, web3
const { prove } = require('./prover') const { prove } = require('./prover')
const FIELD_SIZE = '21888242871839275222246405745257275088548364400416034343698204186575808495617'
const MERKLE_TREE_HEIGHT = 5 const MERKLE_TREE_HEIGHT = 5
const RPC_URL = 'http://localhost:8545' 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() { async function buildMerkleTree() {
console.log('Getting contract state...') console.log('Getting contract state...')
const events = await contract.getPastEvents('NewCommitment', { fromBlock: 0, toBlock: 'latest' }) const events = await contract.getPastEvents('NewCommitment', { fromBlock: 0, toBlock: 'latest' })
const leaves = events const leaves = events
.sort((a, b) => a.returnValues.index - b.returnValues.index) // todo sort by event date .sort((a, b) => a.returnValues.index - b.returnValues.index) // todo sort by event date
.map((e) => toFixedHex(e.returnValues.commitment)) .map((e) => toFixedHex(e.returnValues.commitment))
console.log('leaves', leaves) // console.log('leaves', leaves)
return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 }) return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 })
} }
async function insertOutput(tree, output) { async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer }) {
await tree.insert(output.commitment) // todo shuffle inputs and outputs
let { pathElements, pathIndices } = await tree.path(tree.elements().length - 1) if (inputs.length !== 2 || outputs.length !== 2 ) {
output.merklePathIndices = pathIndices throw new Error('Unsupported number of inputs/outputs')
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)
} }
await insertOutput(tree, output1) let inputMerklePathIndices = []
await insertOutput(tree, output2) 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 = { let input = {
root: oldRoot, root: oldRoot,
newRoot: tree.root(), newRoot: tree.root(),
inputNullifier: [input1.nullifier, input2.nullifier], inputNullifier: inputs.map(x => x.getNullifier()),
outputCommitment: [outputs1.commitment, outputs2.commitment], outputCommitment: outputs.map(x => x.getCommitment()),
extAmount, extAmount,
fee, fee,
extData, extDataHash,
// private inputs
privateKey: inputs1.privkey, // TODO make sure you use the right one when you shuffle inputs
// data for 2 transaction inputs // data for 2 transaction inputs
inAmount: [inputs1.amount, inputs2.amount], inAmount: inputs.map(x => x.amount),
inBlinding: [inputs1.blinding, inputs2.blinding], inPrivateKey: inputs.map(x => x.privkey),
inPathIndices: [bitsToNumber(inputs1.merklePathIndices), bitsToNumber(inputs2.merklePathIndices)], inBlinding: inputs.map(x => x.blinding),
inPathElements: [inputs1.merklePathElements, inputs2.merklePathElements], inPathIndices: inputMerklePathIndices,
inPathElements: inputMerklePathElements,
// data for 2 transaction outputs // data for 2 transaction outputs
outAmount: [outputs1.amount, outputs2.amount], outAmount: outputs.map(x => x.amount),
outBlinding: [outputs1.blinding, outputs2.blinding], outBlinding: outputs.map(x => x.blinding),
outPubkey: [outputs1.pubkey, outputs2.pubkey], outPubkey: outputs.map(x => x.pubkey),
outPathIndices: bitsToNumber[outputs1.merklePathIndices.slice(1)], outPathIndices: outputIndex >> 1,
outPathElements: [outputs1.merklePathElements.slice(1)], outPathElements: outputPath,
} }
console.log('SNARK input', input) //console.log('SNARK input', input)
console.log('Generating SNARK proof...') console.log('Generating SNARK proof...')
const proof = await prove(input, './artifacts/circuits/transaction') const proof = await prove(input, './artifacts/circuits/transaction')
@ -131,12 +92,14 @@ async function getProof({ input1, input2, output1, output2, tree, extAmount, fee
const args = [ const args = [
toFixedHex(input.root), toFixedHex(input.root),
toFixedHex(input.newRoot), toFixedHex(input.newRoot),
[toFixedHex(input1.nullifier), toFixedHex(input2.nullifier)], inputs.map(x => toFixedHex(x.getNullifier())),
[toFixedHex(output1.commitment), toFixedHex(output2.commitment)], outputs.map(x => toFixedHex(x.getCommitment())),
toFixedHex(0), toFixedHex(extAmount),
toFixedHex(input.fee), toFixedHex(fee),
toFixedHex(extData), // extData hash actually extData,
toFixedHex(extDataHash),
] ]
// console.log('Solidity args', args)
return { return {
proof, proof,
@ -146,16 +109,13 @@ async function getProof({ input1, input2, output1, output2, tree, extAmount, fee
async function deposit() { async function deposit() {
const amount = 1e6 const amount = 1e6
const tree = await buildMerkleTree() const inputs = [new Utxo(), new Utxo()]
const keypair = randomKeypair() const outputs = [new Utxo({ amount }), new Utxo()]
const tx = createDeposit(amount, keypair)
const { proof, args } = await getProof({ const { proof, args } = await getProof({
input1: tx.inputs[0], inputs,
input2: tx.inputs[1], outputs,
output1: tx.outputs[1], tree: await buildMerkleTree(),
output2: tx.outputs[1],
tree,
extAmount: amount, extAmount: amount,
fee: 0, fee: 0,
recipient: 0, recipient: 0,
@ -167,155 +127,47 @@ async function deposit() {
.transaction(proof, ...args) .transaction(proof, ...args)
.send({ value: amount, from: web3.eth.defaultAccount, gas: 1e6 }) .send({ value: amount, from: web3.eth.defaultAccount, gas: 1e6 })
console.log(`Receipt ${receipt.transactionHash}`) console.log(`Receipt ${receipt.transactionHash}`)
return tx.outputs[0] return outputs[0]
} }
async function transact(txOutput) { async function transact(utxo) {
console.log('txOutput', txOutput) const inputs = [utxo, new Utxo()]
const tree = await buildMerkleTree() const outputs = [
console.log('tree', tree) new Utxo({ amount: utxo.amount / 4 }),
const oldRoot = await tree.root() new Utxo({ amount: utxo.amount * 3 / 4, privkey: utxo.privkey}),
const keypair = randomKeypair() ]
const index = await tree.indexOf(toFixedHex(txOutput.commitment)) const { proof, args } = await getProof({
console.log('index', index) inputs,
const { pathElements, pathIndices } = await tree.path(index) outputs,
console.log('pathIndices', pathIndices) tree: await buildMerkleTree(),
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],
extAmount: 0, extAmount: 0,
fee: 0, fee: 0,
recipient: 0, recipient: 0,
relayer: 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...') console.log('Sending transfer transaction...')
const receipt = await contract.methods const receipt = await contract.methods
.transaction(proof, ...args) .transaction(proof, ...args)
.send({ from: web3.eth.defaultAccount, gas: 1e6 }) .send({ from: web3.eth.defaultAccount, gas: 1e6 })
console.log(`Receipt ${receipt.transactionHash}`) console.log(`Receipt ${receipt.transactionHash}`)
return tx.outputs[0] return outputs[0]
} }
async function withdraw(txOutput) { async function withdraw(utxo) {
console.log('txOutput', txOutput) const inputs = [utxo, new Utxo()]
const tree = await buildMerkleTree() const outputs = [new Utxo(), new Utxo()]
const oldRoot = await tree.root()
const index = await tree.indexOf(toFixedHex(txOutput.commitment)) const { proof, args } = await getProof({
console.log('index', index) inputs,
const { pathElements, pathIndices } = await tree.path(index) outputs,
console.log('pathIndices', pathIndices) tree: await buildMerkleTree(),
txOutput.merklePathElements = pathElements extAmount: FIELD_SIZE.sub(utxo.amount),
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)),
fee: 0, fee: 0,
recipient: '0xc2Ba33d4c0d2A92fb4f1a07C273c5d21E688Eb48', recipient: '0xc2Ba33d4c0d2A92fb4f1a07C273c5d21E688Eb48',
relayer: 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('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...') console.log('Sending withdraw transaction...')
const receipt = await contract.methods const receipt = await contract.methods
@ -333,12 +185,12 @@ async function main() {
}) })
netId = await web3.eth.net.getId() netId = await web3.eth.net.getId()
const contractData = require('../artifacts/contracts/TornadoPool.sol/TornadoPool.json') 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] web3.eth.defaultAccount = (await web3.eth.getAccounts())[0]
const txOutput = await deposit() const utxo1 = await deposit()
const txOutput1 = await transact(txOutput) const utxo2 = await transact(utxo1)
await withdraw(txOutput1) await withdraw(utxo2)
} }
main() main()

View File

@ -8,7 +8,7 @@ const exec = util.promisify(require('child_process').exec)
function prove(input, keyBasePath) { function prove(input, keyBasePath) {
input = utils.stringifyBigInts(input) input = utils.stringifyBigInts(input)
console.log('input', input) // console.log('input', input)
return tmp.dir().then(async (dir) => { return tmp.dir().then(async (dir) => {
dir = dir.path dir = dir.path
let out let out
@ -28,6 +28,8 @@ function prove(input, keyBasePath) {
out = await exec( out = await exec(
`zkutil prove -c ${keyBasePath}.r1cs -p ${keyBasePath}.params -w ${dir}/witness.json -r ${dir}/proof.json -o ${dir}/public.json`, `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) { } catch (e) {
console.log(out, e) console.log(out, e)
throw e throw e

View File

@ -1,7 +1,7 @@
const crypto = require('crypto') const crypto = require('crypto')
const ethers = require('ethers') const ethers = require('ethers')
const BigNumber = ethers.BigNumber const BigNumber = ethers.BigNumber
const { poseidon } = require('circomlib') const {poseidon} = require('circomlib')
const poseidonHash = (items) => BigNumber.from(poseidon(items).toString()) const poseidonHash = (items) => BigNumber.from(poseidon(items).toString())
const poseidonHash2 = (a, b) => poseidonHash([a, b]) const poseidonHash2 = (a, b) => poseidonHash([a, b])
@ -12,13 +12,19 @@ const FIELD_SIZE = BigNumber.from(
/** Generate random number of specified byte length */ /** Generate random number of specified byte length */
const randomBN = (nbytes = 31) => BigNumber.from(crypto.randomBytes(nbytes)) 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( const encodedData = abi.encode(
['address', 'address', 'bytes', 'bytes'], ['tuple(address recipient,address relayer,bytes encryptedOutput1,bytes encryptedOutput2)'],
[toFixedHex(recipient, 20), toFixedHex(relayer, 20), encryptedOutput1, encryptedOutput2], [{
recipient: toFixedHex(recipient, 20),
relayer: toFixedHex(relayer, 20),
encryptedOutput1: encryptedOutput1,
encryptedOutput2: encryptedOutput2,
}],
) )
const hash = ethers.utils.keccak256(encodedData) const hash = ethers.utils.keccak256(encodedData)
return BigNumber.from(hash).mod(FIELD_SIZE) return BigNumber.from(hash).mod(FIELD_SIZE)
} }
@ -39,18 +45,9 @@ const toBuffer = (value, length) =>
'hex', 'hex',
) )
function bitsToNumber(bits) {
let result = 0
for (const item of bits.slice().reverse()) {
result = (result << 1) + item
}
return result
}
module.exports = { module.exports = {
FIELD_SIZE, FIELD_SIZE,
randomBN, randomBN,
bitsToNumber,
toFixedHex, toFixedHex,
toBuffer, toBuffer,
poseidonHash, poseidonHash,

46
src/utxo.js Normal file
View 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