sdk-monorepo/src/lib/crypto.ts

162 lines
5.4 KiB
TypeScript

// crypto types
import * as Types from 'types/sdk/crypto'
// External crypto
import circomlib from 'circomlib'
import { bigInt } from 'snarkjs'
import { Groth16 } from 'websnark/src/groth16'
import { buildGroth16 } from 'websnark'
import { MerkleTree } from 'fixed-merkle-tree'
import { genWitnessAndProve, toSolidityInput } from 'websnark/src/utils'
// Some utils to work with hex numbers
import { HexUtils, NumberUtils } from 'lib/utils'
// Parse some files
import { Files } from 'lib/data'
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETUP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Several objects have to be set up, like the groth16 prover. All related types are
* (will be) contained within this namespace.
*/
export namespace Setup {
export async function getProvingKey(): Promise<ArrayBufferLike> {
return (await Files.loadRaw('circuits/tornadoProvingKey.bin')).buffer
}
export async function getTornadoCircuit(): Promise<Buffer> {
return await Files.loadRaw('circuits/tornado.json')
}
/**
* @note The following is a comment from tornado-cli: `groth16 initialises a lot of Promises that will never be resolved, that's why we need to use process.exit to terminate the CLI`. They literally didn't check the code to see that these are just worker threads and that `groth16` has a `terminate()` function to remove them. 🤦
*/
export async function getGroth16(): Promise<Groth16> {
const defaultParams = { wasmInitialMemory: 5000 }
return await buildGroth16(defaultParams)
}
}
export namespace Primitives {
export function calcPedersenHash(
pedersenHashData: Types.InputFor.PedersenHash
): Types.OutputOf.PedersenHash {
return circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(pedersenHashData.msg))[0]
}
export function createNote(msg: Buffer): string {
return HexUtils.bufferToHex(msg, 62)
}
export function parseNote(hexNote: string): Types.ZKDepositData {
const _hexNote = hexNote.split('_')[1] ?? hexNote
const buffer = Buffer.from(_hexNote, 'hex')
return createDeposit({
// @ts-expect-error
nullifier: bigInt.leBuff2int(buffer.subarray(0, 31)),
// @ts-expect-error
secret: bigInt.leBuff2int(buffer.subarray(32, 62))
})
}
export function createDeposit(input?: Types.InputFor.CreateDeposit): Types.ZKDepositData {
if (!input?.nullifier || !input?.secret)
input = {
nullifier: NumberUtils.randomBigInteger(31),
secret: NumberUtils.randomBigInteger(31)
}
// @ts-expect-error
let preimage = Buffer.concat([input.nullifier.leInt2Buff(31), input.secret.leInt2Buff(31)])
let commitment = calcPedersenHash({ msg: preimage })
let commitmentHex = HexUtils.bigIntToHex(commitment)
// @ts-expect-error
let nullifierHash = calcPedersenHash({ msg: input.nullifier.leInt2Buff(31) })
let nullifierHex = HexUtils.bigIntToHex(nullifierHash)
return {
nullifier: input.nullifier!,
secret: input.secret!,
preimage: preimage,
commitment: commitment,
hexCommitment: commitmentHex,
nullifierHash: nullifierHash,
hexNullifierHash: nullifierHex
}
}
export function buildMerkleTree(inputs: Types.InputFor.BuildMerkleTree): MerkleTree {
return new MerkleTree(inputs.height, inputs.leaves)
}
export async function calcDepositProofs(
inputs: Array<Types.InputFor.ZKProof>
): Promise<Array<Array<string>>> {
const proofs: string[][] = []
const groth16 = await Setup.getGroth16()
const circuit = await Setup.getTornadoCircuit()
const provingKey = await Setup.getProvingKey()
for (let i = 0, len = inputs.length; i < len; i++) {
const input = inputs[i]
// Compute Merkle Proof
const { pathElements, pathIndex } = input.public.tree.path(input.public.leafIndex)
proofs.push([])
const proofData = await genWitnessAndProve(
groth16,
{
// Public inputs
root: input.public.root,
// @ts-expect-error
nullifierHash: bigInt(input.public.hexNullifierHash),
// @ts-expect-error
recipient: bigInt(input.public.recipientAddress),
// @ts-expect-error
relayer: bigInt(input.public.relayerAddress),
// @ts-expect-error
fee: bigInt(input.public.fee),
//
refund: input.public.refund,
// Private inputs
nullifier: input.private.nullifier,
secret: input.private.secret,
pathElements: pathElements,
pathIndices: pathIndex
},
circuit,
provingKey
)
proofs[i].push(toSolidityInput(proofData).proof)
proofs[i].push(
input.public.root,
input.public.hexNullifierHash,
'0x' + HexUtils.prepareAddress(input.public.recipientAddress, 20),
'0x' + HexUtils.prepareAddress(input.public.relayerAddress, 20),
HexUtils.bigIntToHex(input.public.fee),
HexUtils.bigIntToHex(input.public.refund)
)
}
// Done. 🤷‍♀️
groth16.terminate()
return proofs
}
}
// TODO: implement and decide whether to add in declarations an ambient namespace and merge it here
// export function buildMerkleTree(deposit: Crypto.TornadoDeposit): Crypto.MerkleTree {}
// export function calcMerkleProof(tree: Crypto.MerkleTree): Crypto.MerkleProof {}
// export function calcDepositProof(merkleProof: Crypto.InputFor.DepositProof): Crypto.OutputOf.DepositProof {}
// Namespace exports
export { Types }