UNPKG

minauth-merkle-membership-plugin

Version:

This package contains an implementation of a simple MinAuth plugin that extends the concept of password authentication into authenticating within sets that provide a level of anonymity. The proofs are built and verified with MINA's `o1js` library & proof

102 lines 5.21 kB
// TODO use logger import { Field, MerkleTree, Cache } from 'o1js'; import * as ZkProgram from './merklemembershipsprogram.js'; import * as A from 'fp-ts/lib/Array.js'; import axios from 'axios'; import { pipe } from 'fp-ts/lib/function.js'; import * as TE from 'fp-ts/lib/TaskEither.js'; import * as NE from 'fp-ts/lib/NonEmptyArray.js'; import * as z from 'zod'; import { fromFailablePromise } from 'minauth/dist/utils/fp/taskeither.js'; /** * With this class you can build proofs and interact with `MerkleMembershipsPlugin`. * The zk-circuit will check knowledge of a secret and its witness in merkle trees. * Proving this knowledge can be understood as proving membership in a set of users. * Because of recursion one can prove membership in multiple sets in one proof. */ export class MembershipsProver { /** * Build a proof for given inputs. * Note that even though TreeWitness is passed as public input, it should not be known to the verifier. * TODO fix the above */ prove(publicInput, secretInput) { const computeBaseProof = ([[root, witness], secret]) => // lay the base layer of the recursive proof fromFailablePromise(() => ZkProgram.Program.baseCase(root, new ZkProgram.PrivateInput({ witness, secret })), 'failed in base case'); // For each pair of inputs (secret and public) add another layer of the recursive proof const computeRecursiveProof = (l) => (sp) => A.foldLeft( // Pattern matching, not actually folding () => TE.right(sp), ([[root, witness], secret], tail) => pipe(fromFailablePromise(() => ZkProgram.Program.inductiveCase(root, sp, new ZkProgram.PrivateInput({ witness, secret })), 'failed in inductive case'), TE.chain((proof) => computeRecursiveProof(tail)(proof))))(l); // actually compute the proof const computeFinalProof = (pl, sl) => { const l = NE.zip(pl, sl); const h = NE.head(l); const t = NE.tail(l); return pipe(computeBaseProof(h), TE.chain(computeRecursiveProof(t))); }; return pipe(TE.Do, TE.tap(() => publicInput.length != secretInput.length ? TE.left('unmatched public/secret input list') : TE.right(undefined)), TE.bind('publicInputNE', () => TE.fromOption(() => 'public input list empty')(NE.fromArray(publicInput))), TE.bind('secretInputNE', () => TE.fromOption(() => 'private input list empty')(NE.fromArray(secretInput))), TE.chain(({ publicInputNE, secretInputNE }) => computeFinalProof(publicInputNE, secretInputNE)), TE.map((finalProof) => finalProof.toJSON())); } /** * Fetch the data necessary to build the proof inputs. * In this case these are Merkle trees related to the roots * passed as arguments. */ fetchPublicInputs(args) { const getRootAndWitness = async (treeRoot, leafIndex) => { // fetch the leaves of the tree const url = `${this.cfg.baseUrl}/getLeaves/${treeRoot .toBigInt() .toString()}`; const resp = await axios.get(url); if (resp.status == 200) { // successfully fetched the leaves const leaves = await z .array(z.string().nullable()) .parseAsync(resp.data); // build the tree and the witness const tree = new MerkleTree(ZkProgram.TREE_HEIGHT); leaves.forEach((leaf, index) => { if (leaf !== null) tree.setLeaf(BigInt(index), Field.from(leaf)); }); const witness = new ZkProgram.TreeWitness(tree.getWitness(leafIndex)); return [new ZkProgram.PublicInput({ merkleRoot: treeRoot }), witness]; } else { // failed to fetch the leaves const body = resp.data; throw `error while getting root and witness: ${body.error}`; } }; return fromFailablePromise(() => // foreach merkle root return the tree root and the witness Promise.all(A.map((args) => getRootAndWitness(args.treeRoot, args.leafIndex))(args)), 'unable to fetch inputs'); } constructor(cfg) { /** This class uses the functionl style interface of the plugin. */ this.__interface_tag = 'fp'; this.cfg = cfg; } /** Compile the underlying zk circuit */ static compile() { // disable cache because of bug in o1js 0.14.1: // you have a verification key acquired by using cached circuit AND // not build a proof locally, // but use a serialized one - it will hang during verification. return fromFailablePromise(() => ZkProgram.Program.compile({ cache: Cache.None })); } static initialize(cfg, { compile = true } = {}) { return TE.apSecond(TE.right(new MembershipsProver(cfg)))(compile ? TE.map(() => undefined)(MembershipsProver.compile()) : TE.right(undefined)); } } MembershipsProver.__interface_tag = 'fp'; MembershipsProver; export default MembershipsProver; //# sourceMappingURL=prover.js.map