jubmoji verifier might work now

This commit is contained in:
AtHeartEngineer
2023-11-05 18:10:12 +03:00
parent 64339161a4
commit d387680cbe
11 changed files with 3815 additions and 2 deletions

26
package-lock.json generated
View File

@@ -22,6 +22,7 @@
"discreetly-claimcodes": "^1.1.5",
"discreetly-interfaces": "^0.1.42",
"dotenv": "^16.3.1",
"elliptic": "^6.5.4",
"ethereumjs-util": "^7.1.5",
"express": "^4.18.2",
"express-basic-auth": "^1.2.1",
@@ -33,7 +34,8 @@
"poseidon-lite": "^0.2.0",
"rlnjs": "^3.2.0",
"snarkjs": "^0.7.1",
"socket.io": "^4.6.2"
"socket.io": "^4.6.2",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@jest/globals": "^29.6.2",
@@ -2392,6 +2394,11 @@
"@types/superagent": "*"
}
},
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@@ -8637,6 +8644,23 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/uuidv4": {
"version": "6.2.13",
"resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz",
"integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==",
"dependencies": {
"@types/uuid": "8.3.4",
"uuid": "8.3.2"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",

View File

@@ -36,6 +36,7 @@
"discreetly-claimcodes": "^1.1.5",
"discreetly-interfaces": "^0.1.42",
"dotenv": "^16.3.1",
"elliptic": "^6.5.4",
"ethereumjs-util": "^7.1.5",
"express": "^4.18.2",
"express-basic-auth": "^1.2.1",
@@ -47,7 +48,8 @@
"poseidon-lite": "^0.2.0",
"rlnjs": "^3.2.0",
"snarkjs": "^0.7.1",
"socket.io": "^4.6.2"
"socket.io": "^4.6.2",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@jest/globals": "^29.6.2",

View File

@@ -0,0 +1,52 @@
import express from 'express';
import type { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { limiter } from '../middleware';
import asyncHandler from 'express-async-handler';
import { addIdentityToIdentityListRooms } from '../../data/db';
import { RoomI } from 'discreetly-interfaces';
import { jubmojiVerifier } from '../../gateways/jubmojis/jubmoji';
import { JubmojiRequestI } from '../../gateways/jubmojis/jubmoji.types';
const router = express.Router();
const prisma = new PrismaClient();
/**
* This code is the API route used to verify the proof submitted by the user.
* It uses the SNARKProof type and idc from the request body to verify the proof. If it is valid,
* it adds the identity to the room and returns the roomId. If it is invalid, it returns an error.
* @param {SNARKProof} proof - The SNARKProof object from the user
* @param {string} idc - The identity commitment of the user
* @returns {void}
*/
router.post(
'/join',
limiter,
asyncHandler(async (req: Request, res: Response) => {
const { proof, idc } = req.body as { proof: JubmojiRequestI; idc: string };
const isValid = await jubmojiVerifier(proof);
if (isValid) {
const room = (await prisma.rooms.findUnique({
where: {
roomId: process.env.JUBMOJI_ROOM_ID ? process.env.JUBMOJI_ROOM_ID : '10212131510919'
}
})) as RoomI;
const addedRoom = await addIdentityToIdentityListRooms([room], idc);
if (addedRoom.length === 0) {
res.status(500).json({
status: 'already-added',
roomIds: []
});
} else {
res.status(200).json({
status: 'valid',
roomIds: [room.roomId]
});
}
} else {
res.status(500).json({ status: 'Invalid Proof' });
}
})
);
export default router;

View File

@@ -0,0 +1,181 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import elliptic from 'elliptic';
import { ZqField } from 'ffjavascript';
import * as hash from 'hash.js';
import { BabyJubJub } from './jubmoji.types';
import { bigIntToHex, hexToBigInt } from './utils';
// Define short Weierstrass parameters
const curveOptions = {
p: '30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001',
prime: null,
a: '10216f7ba065e00de81ac1e7808072c9b8114d6d7de87adb16a0a72f1a91f6a0',
b: '23d885f647fed5743cad3d1ee4aba9c043b4ac0fc2766658a410efdeb21f706e',
g: [
'1fde0a3cac7cb46b36c79f4c0a7a732e38c2c7ee9ac41f44392a07b748a0869f',
'203a710160811d5c07ebaeb8fe1d9ce201c66b970d66f18d0d2b264c195309aa'
],
gRed: false,
n: '60c89ce5c263405370a08b6d0302b0bab3eedb83920ee0a677297dc392126f1',
type: 'short'
};
// Initialize Babyjubjub curve using short Weierstrass parameters
const ShortWeierstrassCurve = elliptic.curve.short;
const curve = new ShortWeierstrassCurve(curveOptions);
const ec = new elliptic.ec({
curve: { curve, g: curve.g, n: curve.n, hash: hash.sha256 }
});
const baseField = new ZqField(
'21888242871839275222246405745257275088548364400416034343698204186575808495617'
);
const scalarField = new ZqField(
'2736030358979909402780800718157159386076813972158567259200215660948447373041'
);
const cofactor = 8;
const scalarFieldBitLength = 251;
export const babyjubjub: BabyJubJub = {
ec,
Fb: baseField,
Fs: scalarField,
cofactor,
scalarFieldBitLength
};
export interface CurvePoint {
x: bigint;
y: bigint;
equals(other: CurvePoint): boolean;
isInfinity(): boolean;
toString(): string;
}
export class WeierstrassPoint implements CurvePoint {
public x: bigint;
public y: bigint;
constructor(x: bigint, y: bigint) {
this.x = x;
this.y = y;
}
equals(other: CurvePoint): boolean {
return this.x.toString() === other.x.toString() && this.y.toString() === other.y.toString();
}
static infinity(): WeierstrassPoint {
return new WeierstrassPoint(BigInt(0), BigInt(0));
}
isInfinity(): boolean {
return this.x === BigInt(0) && this.y === BigInt(0);
}
// Converts from an elliptic.js curve point to a WeierstrassPoint
static fromEllipticPoint(point: any): WeierstrassPoint {
if (point.isInfinity()) {
return this.infinity();
}
return new WeierstrassPoint(
BigInt(point.getX().toString(10)),
BigInt(point.getY().toString(10))
);
}
// Based on conversion formulae: https://www-fourier.univ-grenoble-alpes.fr/mphell/doc-v5/conversion_weierstrass_edwards.html
toEdwards(): EdwardsPoint {
if (this.isInfinity()) {
return EdwardsPoint.infinity();
}
const Fb = baseField;
const malpha = Fb.div(BigInt(168698), BigInt(3));
const mx = BigInt(Fb.sub(BigInt(this.x.toString()), malpha));
const my = BigInt(this.y);
const ex = Fb.div(mx, my);
const ey = Fb.div(Fb.sub(mx, BigInt(1)), Fb.add(mx, BigInt(1)));
return new EdwardsPoint(ex, ey);
}
toString(): string {
return `Weierstrass: (${this.x.toString()}, ${this.y.toString()})`;
}
serialize(): string {
return JSON.stringify({
x: bigIntToHex(this.x),
y: bigIntToHex(this.y)
});
}
static deserialize(serialized: string): WeierstrassPoint {
const { x, y } = JSON.parse(serialized);
return new WeierstrassPoint(hexToBigInt(x), hexToBigInt(y));
}
}
export class EdwardsPoint implements CurvePoint {
public x: bigint;
public y: bigint;
constructor(x: bigint, y: bigint) {
this.x = x;
this.y = y;
}
equals(other: CurvePoint): boolean {
return this.x.toString() === other.x.toString() && this.y.toString() === other.y.toString();
}
static infinity(): EdwardsPoint {
return new EdwardsPoint(BigInt(0), BigInt(1));
}
isInfinity(): boolean {
return this.x === BigInt(0) && this.y === BigInt(1);
}
// Based on conversion formulae: https://www-fourier.univ-grenoble-alpes.fr/mphell/doc-v5/conversion_weierstrass_edwards.html
toWeierstrass(): WeierstrassPoint {
if (this.isInfinity()) {
return WeierstrassPoint.infinity();
}
const Fb = baseField;
const mA = BigInt(168698);
const mB = BigInt(1);
const mx = Fb.div(Fb.add(BigInt(1), this.y), Fb.sub(BigInt(1), this.y));
const my = Fb.div(Fb.add(BigInt(1), this.y), Fb.mul(Fb.sub(BigInt(1), this.y), this.x));
const sx = Fb.div(Fb.add(mx, Fb.div(mA, BigInt(3))), mB);
const sy = Fb.div(my, mB);
return new WeierstrassPoint(sx, sy);
}
toString(): string {
return `Edwards: (${this.x.toString()}, ${this.y.toString()})`;
}
serialize(): string {
return JSON.stringify({
x: bigIntToHex(this.x),
y: bigIntToHex(this.y)
});
}
static deserialize(serialized: string): EdwardsPoint {
const { x, y } = JSON.parse(serialized);
return new EdwardsPoint(hexToBigInt(x), hexToBigInt(y));
}
}

View File

@@ -0,0 +1,45 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { EdwardsPoint, WeierstrassPoint, babyjubjub } from './babyJubjub';
/**
* Converts a private key to a public key on the baby jubjub curve
* @param privKey - The private key to convert
* @returns The public key in Short Weierstrass form
*/
export const privateKeyToPublicKey = (privKey: bigint): WeierstrassPoint => {
const pubKeyPoint = babyjubjub.ec.g.mul(privKey.toString(16));
return WeierstrassPoint.fromEllipticPoint(pubKeyPoint);
};
/**
* Computes public parameters T, U of the membership proof based on the provided R value
* This ensures that T, U were generated appropriately
* See: https://hackmd.io/HQZxucnhSGKT_VfNwB6wOw?view
* @param R - The R value of the membership proof
* @param msgHash - The hash of the message signed by the signature
* @returns - The public parameters T, U
*/
export const computeTUFromR = (
R: EdwardsPoint,
msgHash: bigint
): { T: EdwardsPoint; U: EdwardsPoint } => {
const Fs = babyjubjub.Fs;
const shortR = R.toWeierstrass();
const r = shortR.x % Fs.p;
const rInv = Fs.inv(r);
const ecR = babyjubjub.ec.curve.point(shortR.x.toString(16), shortR.y.toString(16));
const ecT = ecR.mul(rInv.toString(16));
const T = WeierstrassPoint.fromEllipticPoint(ecT);
const G = babyjubjub.ec.curve.g;
const rInvm = Fs.neg(Fs.mul(rInv, msgHash));
const ecU = G.mul(rInvm.toString(16));
const U = WeierstrassPoint.fromEllipticPoint(ecU);
return { T: T.toEdwards(), U: U.toEdwards() };
};

View File

@@ -0,0 +1,80 @@
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { EdwardsPoint } from './babyJubjub';
import { MerkleProof } from './jubmoji.types';
import { hashEdwardsPublicKey, hexToBigInt } from './utils';
import { poseidon2 } from 'poseidon-lite/poseidon2';
export const MERKLE_TREE_DEPTH = 8; // We used a fixed depth merkle tree for now
// Precomputed hashes of zero for each layer of the merkle tree
export const MERKLE_TREE_ZEROS = [
'0',
'14744269619966411208579211824598458697587494354926760081771325075741142829156',
'7423237065226347324353380772367382631490014989348495481811164164159255474657',
'11286972368698509976183087595462810875513684078608517520839298933882497716792',
'3607627140608796879659380071776844901612302623152076817094415224584923813162',
'19712377064642672829441595136074946683621277828620209496774504837737984048981',
'20775607673010627194014556968476266066927294572720319469184847051418138353016',
'3396914609616007258851405644437304192397291162432396347162513310381425243293'
];
/**
* Computes the merkle root based a list of public keys
* Note that public keys must be in Twisted Edwards form
* This is because we only ever use the Merkle Tree for in circuit verification,
* and the circuit only ever uses Twisted Edwards points
* @param pubKeys - The list of public keys to compute the merkle root of in Twisted Edwards form
* @param hashFn - The hash function to use for the merkle tree. Defaults to Poseidon
* @returns - The merkle root
*/
export const computeMerkleRoot = async (pubKeys: EdwardsPoint[]): Promise<bigint> => {
const proof = await computeMerkleProof(pubKeys, 0);
return proof.root;
};
/**
* Generates a merkle proof for a given list of public keys and index
* Once again, all public keys are represented in Twisted Edwards form
* @param pubKeys - The list of public keys to generate the merkle proof for in Twisted Edwards form
* @param index - The index of the public key to generate the merkle proof for
* @param hashFn - The hash function to use for the merkle tree. Defaults to Poseidon
* @returns - The merkle proof
*/
export const computeMerkleProof = async (
pubKeys: EdwardsPoint[],
index: number
): Promise<MerkleProof> => {
// All public keys are hashed before insertion into the tree
const leaves = await Promise.all(pubKeys.map((pubKey) => hashEdwardsPublicKey(pubKey)));
let prevLayer: bigint[] = leaves;
let nextLayer: bigint[] = [];
let pathIndices: number[] = [];
let siblings: bigint[] = [];
for (let i = 0; i < MERKLE_TREE_DEPTH; i++) {
pathIndices.push(index % 2);
const siblingIndex = index % 2 === 0 ? index + 1 : index - 1;
const sibling =
siblingIndex === prevLayer.length ? BigInt(MERKLE_TREE_ZEROS[i]) : prevLayer[siblingIndex];
siblings.push(sibling);
index = Math.floor(index / 2);
for (let j = 0; j < prevLayer.length; j += 2) {
const secondNode =
j + 1 === prevLayer.length ? BigInt(MERKLE_TREE_ZEROS[i]) : prevLayer[j + 1];
const nextNode = poseidon2([prevLayer[j], secondNode]);
nextLayer.push(hexToBigInt(nextNode.toString()));
}
prevLayer = nextLayer;
nextLayer = [];
}
const root = prevLayer[0];
return { root, pathIndices, siblings: siblings };
};

View File

@@ -0,0 +1,128 @@
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import snarkjs from 'snarkjs';
import { EdwardsPoint, WeierstrassPoint } from './babyJubjub';
import { computeTUFromR } from './ecdsa';
import { computeMerkleRoot } from './inputGen';
import { VerificationResult, VerifyArgs, ZKP, ZKPPublicSignals } from './jubmoji.types';
import { deserializeMembershipProof, hexToBigInt } from './utils';
import vkey from './vkey';
import { cardPubKeys } from './pubkeys';
export async function jubmojiVerifier(serializedMembershipProof): Promise<VerificationResult> {
const merkleRoot = await getMerkleRootFromCache(collectionPubKeys);
return await verifyMembership({
proof: deserializeMembershipProof(serializedMembershipProof),
merkleRoot,
sigNullifierRandomness: hexToBigInt(
'6addd8ed78c6fb64157aa768c5a9477db536172929949dc22274e323ccb9'
)
});
}
const collectionPubKeys = cardPubKeys.map((card) => card.pubKeyJub);
const verifyMembership = async ({
proof,
merkleRoot,
merkleRootArgs,
sigNullifierRandomness
}: VerifyArgs): Promise<VerificationResult> => {
if (!merkleRoot && !merkleRootArgs) {
throw new Error('Must provide either merkle root or merkle root args!');
}
const publicSignals = getPublicSignalsFromMembershipZKP(proof.zkp);
let resolvedMerkleRoot;
if (merkleRoot) {
resolvedMerkleRoot = merkleRoot;
} else {
const { pubKeys } = merkleRootArgs!;
const edwardsPubKeys = pubKeys.map((pubKey) => pubKey.toEdwards());
resolvedMerkleRoot = await computeMerkleRoot(edwardsPubKeys);
}
if (resolvedMerkleRoot !== publicSignals.merkleRoot) {
return { verified: false };
}
const { T, U } = computeTUFromR(proof.R, proof.msgHash);
if (!T.equals(publicSignals.T) || !U.equals(publicSignals.U)) {
return { verified: false };
}
if (sigNullifierRandomness !== publicSignals.sigNullifierRandomness) {
return { verified: false };
}
// TODO! This is where we need to check and make sure someone can't join more than once
// if (usedSigNullifiers && usedSigNullifiers.includes(publicSignals.sigNullifier)) {
// return { verified: false };
// }
const verified = await verifyMembershipZKP(vkey, proof.zkp);
if (!verified) {
return { verified: false };
}
return {
verified: true,
consumedSigNullifiers: [publicSignals.sigNullifier]
};
};
/**
* Gets public signals as typed arguments from a membership zkp
* @param zkp - The membership zkp
* @returns - Public signals of the membership zkp
*/
const getPublicSignalsFromMembershipZKP = (zkp: ZKP): ZKPPublicSignals => {
const publicSignals = zkp.publicSignals;
return {
merkleRoot: BigInt(publicSignals[3]),
T: new EdwardsPoint(BigInt(publicSignals[4]), BigInt(publicSignals[5])),
U: new EdwardsPoint(BigInt(publicSignals[6]), BigInt(publicSignals[7])),
sigNullifier: BigInt(publicSignals[0]),
sigNullifierRandomness: BigInt(publicSignals[8]),
pubKeyNullifier: BigInt(publicSignals[1]),
pubKeyNullifierRandomnessHash: BigInt(publicSignals[2])
};
};
const getMerkleRootFromCache = async (pubKeyList: string[]): Promise<bigint> => {
const pubKeys = pubKeyList.map((pubKey) => publicKeyFromString(pubKey).toEdwards());
return await computeMerkleRoot(pubKeys);
};
/**
* Converts a public key in hex form to a WeierstrassPoint
* Reference for key format: https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
* @param pubKey - The public key in hex form
* @returns The public key in Weierstrass form
*/
const publicKeyFromString = (pubKey: string): WeierstrassPoint => {
if (!pubKey.startsWith('04')) {
throw new Error('Only handle uncompressed public keys for now');
}
const pubKeyLength = pubKey.length - 2;
const x = hexToBigInt(pubKey.slice(2, 2 + pubKeyLength / 2));
const y = hexToBigInt(pubKey.slice(2 + pubKeyLength / 2));
return new WeierstrassPoint(x, y);
};
/**
* Verifies a zero knowledge proof for a membership proof
* @param vkey - The verification key for the membership proof
* @param proof - The zero knowledge proof to verify
* @param publicInputs - The public inputs to the zero knowledge proof
* @returns - A boolean indicating whether or not the proof is valid
*/
const verifyMembershipZKP = async (vkey: any, { proof, publicSignals }: ZKP): Promise<boolean> => {
return await snarkjs.groth16.verify(vkey, publicSignals, proof);
};

View File

@@ -0,0 +1,284 @@
/* eslint-disable @typescript-eslint/prefer-function-type */
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import { ZqField } from 'ffjavascript';
import { EdwardsPoint, WeierstrassPoint } from './babyJubjub';
import { SNARKProof } from '../../types';
export interface JubmojiRequestI {
R: string;
msgHash: string;
zkp: SNARKProof;
}
export type BabyJubJub = {
ec: any;
Fb: ZqField;
Fs: ZqField;
cofactor: number;
scalarFieldBitLength: number;
};
export type Signature = {
r: bigint;
s: bigint;
};
// Contents of a proof for demonstrating a valid BabyJubjub ECDSA
// signature without revealing the signature's s value
// Based on the Efficient ECDSA formulation: https://personaelabs.org/posts/efficient-ecdsa-1/
export interface MembershipProof {
R: EdwardsPoint;
msgHash: bigint;
zkp: ZKP;
}
export interface PublicInputs {
R: EdwardsPoint;
T: EdwardsPoint;
U: EdwardsPoint;
}
// Arguments needed to compute a merkle proof
export interface MerkleProofArgs {
pubKeys: WeierstrassPoint[];
index: number;
hashFn?: any;
}
// Arguments needed to batch compute merkle proofs
export interface BatchMerkleProofArgs {
pubKeys: WeierstrassPoint[];
indices: number[];
hashFn?: any;
}
// Arguments needed to compute a merkle root
export interface MerkleRootArgs {
pubKeys: WeierstrassPoint[];
hashFn?: any;
}
// Arguments needed to generate a membership proof
export interface ProveArgs {
sig: Signature;
msgHash: bigint;
publicInputs?: PublicInputs;
pubKey?: WeierstrassPoint;
merkleProof?: MerkleProof;
merkleProofArgs?: MerkleProofArgs;
sigNullifierRandomness: bigint;
pubKeyNullifierRandomness: bigint;
pathToCircuits?: string;
enableTiming?: boolean;
}
// Arguments needed to batch generate membership proofs
export interface BatchProveArgs {
sigs: Signature[];
msgHashes: bigint[];
publicInputs?: PublicInputs[];
pubKeys?: WeierstrassPoint[];
merkleProofs?: MerkleProof[];
merkleProofArgs?: BatchMerkleProofArgs;
sigNullifierRandomness: bigint;
pubKeyNullifierRandomness: bigint;
pathToCircuits?: string;
enableTiming?: boolean;
}
// Arguments needed to verify a membership proof
export interface VerifyArgs {
proof: MembershipProof;
merkleRoot?: bigint;
merkleRootArgs?: MerkleRootArgs;
sigNullifierRandomness: bigint;
usedSigNullifiers?: bigint[];
pathToCircuits?: string;
enableTiming?: boolean;
}
// Arguments needed to batch verify membership proofs
export interface BatchVerifyArgs {
proofs: MembershipProof[];
merkleRoot?: bigint;
merkleRootArgs?: MerkleRootArgs;
sigNullifierRandomness: bigint;
usedSigNullifiers?: bigint[];
pathToCircuits?: string;
enableTiming?: boolean;
}
export type VerificationResult = {
verified: boolean;
consumedSigNullifiers?: bigint[];
};
// Zero knowledge proof generated by snarkjs
export type ZKP = { proof: any; publicSignals: string[] };
// Inputs to the membership proof circuit
// Similar to inputs for Spartan-ecdsa membership circuit:
// https://github.com/personaelabs/spartan-ecdsa/blob/main/packages/circuits/eff_ecdsa_membership/pubkey_membership.circom
// Includes nullifierRandomness for generating unique nullifiers
export type ZKPInputs = {
s: bigint;
root: bigint;
Tx: bigint;
Ty: bigint;
Ux: bigint;
Uy: bigint;
pathIndices: number[];
siblings: bigint[];
sigNullifierRandomness: bigint;
pubKeyNullifierRandomness: bigint;
};
// Typed public signals for the membership proof circuit
export type ZKPPublicSignals = {
merkleRoot: bigint;
T: EdwardsPoint;
U: EdwardsPoint;
sigNullifier: bigint;
sigNullifierRandomness: bigint;
pubKeyNullifier: bigint;
pubKeyNullifierRandomnessHash: bigint;
};
export interface MerkleProof {
root: bigint;
pathIndices: number[];
siblings: bigint[];
}
export type Jubmoji = {
pubKeyIndex: number; // Index of the card's public key within the list of public keys
sig: string; // DER-encoded signature
// msgNonce and msgRand are the counter and randomness used to generate signatures
// See: https://github.com/arx-research/libhalo/blob/master/docs/halo-command-set.md#command-sign_random
msgNonce: number;
msgRand: string;
// R, T, U are serialized points on the BabyJubjub curve represented in Edwards form.
// They are based on the Efficient ECDSA formulation: https://personaelabs.org/posts/efficient-ecdsa-1/
R: string;
T: string;
U: string;
};
export type NfcCardRawSignature = {
r: string;
s: string;
v: 27 | 28;
};
// Result of signing a message with an Arx card
export type NfcCardSignMessageResult = {
digest: string;
rawSig: NfcCardRawSignature;
pubKey: string;
};
// Defines a class used to create and verify proofs
export interface ProofClass<A, P> {
prove(proofArgs: A): Promise<P>;
verify(proof: P): Promise<VerificationResult>;
}
// Defines a constructor for a proof class
// C: Arguments needed to construct the proof class. Should be JSON.stringify-able
// A: Arguments needed to prove the proof
// P: The proof. Should be JSON.stringify-able
export interface ProofClassConstructor<C, A, P> {
new (classArgs: C): ProofClass<A, P>;
}
// Creates an instance of a proof class
export function createProofInstance<C, A, P>(
constructor: ProofClassConstructor<C, A, P>,
args: C
): ProofClass<A, P> {
return new constructor(args);
}
export interface JubmojiInCollectionClassArgs {
collectionPubKeys: string[];
sigNullifierRandomness: string;
pathToCircuits?: string;
}
export interface JubmojiInCollectionProofArgs {
jubmoji: Jubmoji;
}
export interface JubmojiInCollectionProof {
serializedMembershipProof: string;
usedSigNullifiers?: string[];
}
export interface JubmojiInCollectionWithNonceClassArgs {
collectionPubKeys: string[];
sigNullifierRandomness: string;
pathToCircuits?: string;
}
export interface JubmojiInCollectionWithNonceProofArgs {
jubmoji: Jubmoji;
}
export interface JubmojiInCollectionWithNonceProof {
serializedMembershipProof: string;
msgNonce: number;
msgRand: string;
usedSigNullifiers?: string[];
}
export interface NUniqueJubmojiInCollectionClassArgs {
collectionPubKeys: string[];
sigNullifierRandomness: string;
N: number;
pathToCircuits?: string;
}
export interface NUniqueJubmojiInCollectionProofArgs {
jubmojis: Jubmoji[];
}
export interface NUniqueJubmojiInCollectionProof {
serializedMembershipProofs: string[];
usedSigNullifiers?: string[];
}
export interface PublicMessageSignatureClassArgs {
randStr?: string;
}
export interface PublicMessageSignatureProofArgs {
message: string;
rawSig: NfcCardRawSignature;
pubKeyIndex: number;
}
export interface PublicMessageSignatureProof {
message: string;
rawSig: NfcCardRawSignature;
pubKeyIndex: number;
}
export interface TeamLeaderboardClassArgs {
teamPubKeys: string[];
collectionPubKeys: string[];
sigNullifierRandomness: string;
pathToCircuits?: string;
}
export interface TeamLeaderboardProofArgs {
teamJubmoji: Jubmoji;
collectionJubmojis: Jubmoji[];
}
export interface TeamLeaderboardProof {
teamPubKeyIndex: number;
serializedTeamMembershipProof: string;
serializedCollectionMembershipProofs: string[];
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { poseidon2 } from 'poseidon-lite/poseidon2';
import { EdwardsPoint } from './babyJubjub';
import { MembershipProof } from './jubmoji.types';
export const hexToBigInt = (hex: string): bigint => {
return BigInt(`0x${hex}`);
};
export const bigIntToHex = (bigInt: bigint): string => {
return bigInt.toString(16);
};
/**
* Hashes an EdwardsPoint to a bigint. Uses the Poseidon hash function
* @param pubKey - The public key to hash
* @param hashFn - Optional hash function to use. Defaults to Poseidon
* @returns The hash of the public key
*/
export const hashEdwardsPublicKey = (pubKey: EdwardsPoint): bigint => {
const hash = poseidon2([pubKey.x, pubKey.y]);
return hexToBigInt(hash.toString());
};
export const deserializeMembershipProof = (serializedProof: string): MembershipProof => {
const proof = JSON.parse(serializedProof);
const R = EdwardsPoint.deserialize(proof.R);
const msgHash = hexToBigInt(proof.msgHash);
const zkp = proof.zkp;
return { R, msgHash, zkp };
};

View File

@@ -0,0 +1,127 @@
const vkey = {
protocol: 'groth16',
curve: 'bn128',
nPublic: 9,
vk_alpha_1: [
'20491192805390485299153009773594534940189261866228447918068658471970481763042',
'9383485363053290200918347156157836566562967994039712273449902621266178545958',
'1'
],
vk_beta_2: [
[
'6375614351688725206403948262868962793625744043794305715222011528459656738731',
'4252822878758300859123897981450591353533073413197771768651442665752259397132'
],
[
'10505242626370262277552901082094356697409835680220590971873171140371331206856',
'21847035105528745403288232691147584728191162732299865338377159692350059136679'
],
['1', '0']
],
vk_gamma_2: [
[
'10857046999023057135944570762232829481370756359578518086990519993285655852781',
'11559732032986387107991004021392285783925812861821192530917403151452391805634'
],
[
'8495653923123431417604973247489272438418190587263600148770280649306958101930',
'4082367875863433681332203403145435568316851327593401208105741076214120093531'
],
['1', '0']
],
vk_delta_2: [
[
'10857046999023057135944570762232829481370756359578518086990519993285655852781',
'11559732032986387107991004021392285783925812861821192530917403151452391805634'
],
[
'8495653923123431417604973247489272438418190587263600148770280649306958101930',
'4082367875863433681332203403145435568316851327593401208105741076214120093531'
],
['1', '0']
],
vk_alphabeta_12: [
[
[
'2029413683389138792403550203267699914886160938906632433982220835551125967885',
'21072700047562757817161031222997517981543347628379360635925549008442030252106'
],
[
'5940354580057074848093997050200682056184807770593307860589430076672439820312',
'12156638873931618554171829126792193045421052652279363021382169897324752428276'
],
[
'7898200236362823042373859371574133993780991612861777490112507062703164551277',
'7074218545237549455313236346927434013100842096812539264420499035217050630853'
]
],
[
[
'7077479683546002997211712695946002074877511277312570035766170199895071832130',
'10093483419865920389913245021038182291233451549023025229112148274109565435465'
],
[
'4595479056700221319381530156280926371456704509942304414423590385166031118820',
'19831328484489333784475432780421641293929726139240675179672856274388269393268'
],
[
'11934129596455521040620786944827826205713621633706285934057045369193958244500',
'8037395052364110730298837004334506829870972346962140206007064471173334027475'
]
]
],
IC: [
[
'5906523767025352576019539800575526703999785044312911592948973442389199126505',
'5399516258430932355535174434944180731688115835606520831984100718735405595772',
'1'
],
[
'9790477188945059076442519292795257867957347672288790978218244312669794270325',
'624485857042273351177690254178225395163543252084408929924426132565147437366',
'1'
],
[
'3479995447129150108900208244025267654247990557816586426180247323199495647349',
'17304109161574863119413208339549874476577725501798123988351223506303640088143',
'1'
],
[
'3054209688638680605336774061629468549369097307440376949057827999544092296420',
'5049000941597301571356507935915602177222642901014937911117181408531106793240',
'1'
],
[
'3768309033651830981078461634376711978666409179768543356075076042345846419822',
'14232067096259050863334433214165468424370193127742466523583523730020905109183',
'1'
],
[
'5910156264669607430882933403494838472555501111573345279966258564796719456837',
'10386898818693886233797834042450562283730425306753976504811645840997283281593',
'1'
],
[
'5848514880539808887001034866489273124040608404072979651845830442053600850472',
'17995743802025070060480075195453000874769970519239751898841530116119494187156',
'1'
],
[
'19407199380583974063335319513369108311590348654020408498405117118250681465433',
'10447798841946339789352050710608266132168605671188376395009160862498349470228',
'1'
],
[
'18746800065979837074894156943440632661656502322627312702866419198947336958616',
'6057882634184253227568238012793960005434954857795973519013159759890297958086',
'1'
],
[
'7369167653274471663233926616774246947916353554158573320045358175706593604250',
'4271979081058356268893546335109696483068377530236220151699523589701419647715',
'1'
]
]
};
export default vkey;