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
JavaScript
// 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