sdk-monorepo/@tornado/sdk-crypto/src/index.ts

297 lines
8.8 KiB
TypeScript

// External crypto
import circomlib, { mimcsponge } from 'circomlib'
import { bigInt } from 'snarkjs'
import { Groth16 } from 'websnark/src/groth16'
import { buildGroth16 } from 'websnark'
import { MerkleTree, Element, HashFunction } from 'fixed-merkle-tree'
import { genWitnessAndProve, toSolidityInput } from 'websnark/src/utils'
// Some utils to work with hex numbers
import { ErrorUtils, HexUtils, NumberUtils } from '@tornado/sdk-utils'
// Parse some files
import { Files, Json } from '@tornado/sdk-data'
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DECLARATIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Interfaces for the cryptographic primitives.
export type bigInt = typeof bigInt
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OUTPUTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* These are types which define and scope (!) the outputs of the cryptograhic
* functions. They have been scoped such that there isn't much naming confusion
* between what a function takes and not. Common aliases are used there where deemed
* appropriate.
*/
export namespace OutputOf {
export type PedersenHash = bigInt
export interface CreateDeposit {
nullifier: bigInt
secret: bigInt
preimage: Buffer
commitment: PedersenHash
hexCommitment: string
nullifierHash: PedersenHash
hexNullifierHash: string
}
export interface MerkleProof {
root: any
path: {
indices: any
elements: any[]
}
}
export interface Groth16Proof {
pi_a: string
pi_b: string
pi_c: string
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OUTPUT ALIASES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* There may be also multiple output aliases for one output, to make the code more readable,
* in a certain sense of speaking. The delimiter below just makes this comment not pass on to
* `MerkleTree`.
*/
type __OutputAliasDelimiter = null
export type MerkleProof = OutputOf.MerkleProof
export type DepositProof = OutputOf.Groth16Proof
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INPUTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* These are types which define and scope (!) the inputs of the cryptograhic
* functions. The same as above applies, but note that some outputs are inputs
* for other functions. For example, MerkleTree is an output of the function
* which builds the Tornado Cash merkle tree, and this is passed as input,
* to the merkle proof builder.
*/
export namespace InputFor {
export interface PedersenHash {
msg: Buffer
}
export interface CreateDeposit {
nullifier?: bigInt
secret?: bigInt
}
export interface BuildMerkleTree {
height: number
leaves: Array<Element>
}
export interface DepositProof {
public: {
root: Element
tree: MerkleTree
leafIndex: number
hexNullifierHash: string
recipientAddress: string
relayerAddress: string
fee: bigInt
refund: bigInt
}
private: {
nullifier: bigInt
secret: bigInt
}
}
interface PublicGroth16 {
root: any
nullifierHash: PedersenHash
recipient: bigInt
relayer: bigInt
refund: bigInt
fee: bigInt
}
interface PrivateGroth16 {
nullifier: bigInt
secret: bigInt
pathIndices: number[]
pathElements: string[]
}
export type Groth16 = PublicGroth16 & PrivateGroth16
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INPUT ALIASES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* There may be also multiple input aliases for one input, to make the code more readable,
* in a certain sense of speaking. The delimiter below just makes this comment not pass on to
* `TornadoDeposit`.
*/
type __InputAliasDelimiter = null
export type DepositInfo = OutputOf.CreateDeposit
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 function getZeroElement(): string {
return '21663839004416932945382355908790599225266501822907911457504978515578255421292'
}
export function getDefaultHash(): HashFunction<Element> {
// @ts-ignore
return (left, right) => mimcsponge.multiHash([bigInt(left), bigInt(right)]).toString()
}
export function getProvingKey(): ArrayBufferLike {
return Files.loadRawSync('circuits/tornadoProvingKey.bin').buffer
}
export function getTornadoCircuit(): any {
return Json.loadSync('circuits/tornado.json')
}
let cachedGroth16Prover: Groth16 | null = null
export async function getGroth16(): Promise<Groth16> {
const defaultParams = { wasmInitialMemory: 5000 }
if (!cachedGroth16Prover) cachedGroth16Prover = await buildGroth16(defaultParams)
return cachedGroth16Prover
}
export function terminateGroth16(): void {
cachedGroth16Prover!.terminate()
cachedGroth16Prover = null
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Primitives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* These are the important cryptographic primitives Tornado Cash uses.
*/
export namespace Primitives {
export function calcPedersenHash(pedersenHashData: InputFor.PedersenHash): 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): DepositInfo {
const _hexNote = hexNote.split('_')[1] ?? hexNote
const buffer = Buffer.from(_hexNote.slice(2), 'hex')
return createDeposit({
// @ts-expect-error
nullifier: bigInt.leBuff2int(buffer.subarray(0, 31)),
// @ts-expect-error
secret: bigInt.leBuff2int(buffer.subarray(31, 62))
})
}
export function createDeposit(
input: InputFor.CreateDeposit = {
nullifier: NumberUtils.randomBigInteger(31),
secret: NumberUtils.randomBigInteger(31)
}
): DepositInfo {
// @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: InputFor.BuildMerkleTree): MerkleTree {
return new MerkleTree(inputs.height, inputs.leaves, {
hashFunction: Setup.getDefaultHash(),
zeroElement: Setup.getZeroElement()
})
}
export async function calcDepositProofs(
inputs: Array<InputFor.DepositProof>
): Promise<Array<Array<string>>> {
const proofs: string[][] = []
const groth16 = await Setup.getGroth16()
const circuit = Setup.getTornadoCircuit()
const provingKey = Setup.getProvingKey()
for (let i = 0, len = inputs.length; i < len; i++) {
const input = inputs[i]
// TODO: remove try and const again after fixing
let proofData
// Compute Merkle Proof
// The ts return is noted as `pathIndex` but using this we get an undefined because it is really `pathIndices`???
// TODO: Bug that needs to be fixed (above)
const { pathElements, pathIndices } = input.public.tree.path(input.public.leafIndex)
proofs.push([])
try {
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: pathIndices
},
circuit,
provingKey
)
} catch (err) {
groth16.terminate()
throw ErrorUtils.ensureError(err)
}
proofs[i].push(toSolidityInput(proofData).proof)
proofs[i].push(
String(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. 🤷‍♀️
Setup.terminateGroth16()
return proofs
}
}