UNPKG

@antefinance/ante-sdk

Version:

Library for interacting with Ante smart contracts

311 lines (276 loc) 8.43 kB
import { BABY_JUB_NEGATIVE_ONE, booleanToBigInt, generateSnarkMessageHash, hexToBigInt, numberToBigInt, uuidToBigInt, } from '@pcd/util'; import { ZKEdDSAEventTicketPCD, ZKEdDSAEventTicketPCDClaim, ZKEdDSAEventTicketPCDPackage, snarkInputForValidEventIds, } from '@pcd/zk-eddsa-event-ticket-pcd'; import knownTicketTypes from './known-ticket-types'; import { encodeAbiParameters } from 'viem'; import { ZUTHAILAND_TICKETS } from './zuthailand-tickets'; export const pcdFromSerializedPcdString = async (serializedPCD: string) => { const parsedPCD = JSON.parse(decodeURIComponent(serializedPCD)); if (parsedPCD.type !== ZKEdDSAEventTicketPCDPackage.name) { throw new Error('Invalid PCD type'); } const pcd = await ZKEdDSAEventTicketPCDPackage.deserialize( decodeURIComponent(serializedPCD) ); return pcd; }; export type GenerateZupassProofInput = { serializedPCDStr: string; watermark: string; externalNullifier: string; }; export const getZupassSigner = (): string[] => { const zupassSigner = loadZupassPublicKeys(); return zupassSigner; }; // implementation reference: https://github.com/proofcarryingdata/zupass/blob/0339fd66a49a49920ce9440c83a2b9372eb05f93/apps/consumer-client/src/pages/examples/zk-eddsa-event-ticket-proof.tsx#L499C1-L536C2 export async function verifyZupassProof( pcd: ZKEdDSAEventTicketPCD, watermark: bigint, externalNullifier: string, expectedEventIds: string[] ): Promise<boolean> { const { verify } = ZKEdDSAEventTicketPCDPackage; const verified = await verify(pcd); if (!verified) return false; // verify the claim is for the correct fields requested, watermark, and externalNullifier const sameExternalNullifier = pcd.claim.externalNullifier === externalNullifier || (!pcd.claim.externalNullifier && !externalNullifier); const sameWatermark = pcd.claim.watermark === watermark.toString(); const validEventExists = expectedEventIds.includes( pcd.claim.partialTicket.eventId! ); return sameExternalNullifier && sameWatermark && validEventExists; } export function convertStringArrayToBigIntArray(arr: string[]): bigint[] { return arr.map((x) => BigInt(x)); } // implementation reference: https://github.com/proofcarryingdata/zupass/blob/0339fd66a49a49920ce9440c83a2b9372eb05f93/packages/pcd/zk-eddsa-event-ticket-pcd/src/ZKEdDSAEventTicketPCD.ts#L539 export function publicSignalsFromClaim( claim: ZKEdDSAEventTicketPCDClaim ): string[] { const t = claim.partialTicket; const ret: string[] = []; const negOne = BABY_JUB_NEGATIVE_ONE.toString(); // Outputs appear in public signals first // position 0 ret.push( t.ticketId === undefined ? negOne : uuidToBigInt(t.ticketId).toString() ); // position 1 ret.push( t.eventId === undefined ? negOne : uuidToBigInt(t.eventId).toString() ); // position 2 ret.push( t.productId === undefined ? negOne : uuidToBigInt(t.productId).toString() ); // position 3 ret.push( t.timestampConsumed === undefined ? negOne : t.timestampConsumed.toString() ); // position 4 ret.push( t.timestampSigned === undefined ? negOne : t.timestampSigned.toString() ); // position 5 ret.push(t.attendeeSemaphoreId || negOne); // position 6 ret.push( t.isConsumed === undefined ? negOne : booleanToBigInt(t.isConsumed).toString() ); // position 7 ret.push( t.isRevoked === undefined ? negOne : booleanToBigInt(t.isRevoked).toString() ); // position 8 ret.push( t.ticketCategory === undefined ? negOne : numberToBigInt(t.ticketCategory).toString() ); // position 9 ret.push( t.attendeeEmail === undefined ? negOne : generateSnarkMessageHash(t.attendeeEmail).toString() ); // position 10 ret.push( t.attendeeName === undefined ? negOne : generateSnarkMessageHash(t.attendeeName).toString() ); // position 11 // Placeholder for reserved field ret.push(negOne); // position 12 ret.push(claim.nullifierHash || negOne); // Public inputs appear in public signals in declaration order // position 13->14 ret.push(hexToBigInt(claim.signer[0]).toString()); ret.push(hexToBigInt(claim.signer[1]).toString()); // position 15->34 for (const eventId of snarkInputForValidEventIds(claim.validEventIds)) { ret.push(eventId); } // position 35 ret.push(claim.validEventIds !== undefined ? '1' : '0'); // checkValidEventIds // position 36 ret.push(claim.externalNullifier!); // position 37 ret.push(claim.watermark); return ret; } export type PublicKeyInfo = { publicKey: string[]; publicKeyName: string; publicKeyType: string; }; export type TicketType = PublicKeyInfo & { eventId: string; productId: string; ticketGroup: string; }; export type KnownTicketTypesResponse = { knownTicketTypes: TicketType[]; publicKeys: PublicKeyInfo[]; }; const knownticketTypesGetterFactory = () => { let knownTicketTypes: TicketType[] | undefined; return async (): Promise<TicketType[]> => { if (knownTicketTypes) { return knownTicketTypes; } knownTicketTypes = await loadKnownTicketTypes(); return knownTicketTypes; }; }; export const getKnownTicketTypes = knownticketTypesGetterFactory(); export const getDistinctEventIds = async (): Promise<string[]> => { const knownTicketTypes = await getKnownTicketTypes(); return Array.from(new Set(knownTicketTypes.map((t) => t.eventId))); }; export const loadKnownTicketTypes = async (): Promise<TicketType[]> => { // https://api.zupass.org/issue/known-ticket-types const value = knownTicketTypes.value; return [...value.knownTicketTypes, ...ZUTHAILAND_TICKETS] as TicketType[]; }; export const verifyWitnessSignature = async ( witness: Witness ): Promise<boolean> => { const publicKeys = getZupassSigner(); const { _pubSignals } = witness; if (_pubSignals.length !== 38) { throw new Error('Invalid public signals length'); } const witnessSigners = _pubSignals.slice(13, 15); const zupassPublicKeys = publicKeys.map((x) => hexToBigInt(x)); return ( zupassPublicKeys.includes(witnessSigners[0]) && zupassPublicKeys.includes(witnessSigners[1]) ); }; export const loadZupassPublicKeys = (): string[] => { //https://api.zupass.org/issue/eddsa-public-key return [ '05e0c4e8517758da3a26c80310ff2fe65b9f85d89dfc9c80e6d0b6477f88173e', '29ae64b615383a0ebb1bc37b3a642d82d37545f0f5b1444330300e4c4eedba3f', // ZuThailand '1ebfb986fbac5113f8e2c72286fe9362f8e7d211dbc68227a468d7b919e75003', '10ec38f11baacad5535525bbe8e343074a483c051aa1616266f3b1df3fb7d204', ]; }; export const validateEventIds = async ( eventIds: string[] ): Promise<boolean> => { const distinctKnownEventIds = await getDistinctEventIds(); return eventIds.every((eventId) => distinctKnownEventIds.includes(eventId)); }; export type Witness = { _pA: [bigint, bigint]; _pB: [[bigint, bigint], [bigint, bigint]]; _pC: [bigint, bigint]; _pubSignals: [ bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, ]; }; export const generateWitness = (pcd: ZKEdDSAEventTicketPCD): Witness => { const _pA = convertStringArrayToBigIntArray( pcd.proof.pi_a.slice(0, 2) ) as Witness['_pA']; const _pB = [ convertStringArrayToBigIntArray(pcd.proof.pi_b[0].slice(0).reverse()), convertStringArrayToBigIntArray(pcd.proof.pi_b[1].slice(0).reverse()), ] as Witness['_pB']; const _pC = convertStringArrayToBigIntArray( pcd.proof.pi_c.slice(0, 2) ) as Witness['_pC']; const _pubSignals = convertStringArrayToBigIntArray( publicSignalsFromClaim(pcd.claim) ) as Witness['_pubSignals']; return { _pA, _pB, _pC, _pubSignals }; }; export const abiEncodeProof = (witness: Witness): `0x${string}` => { const { _pA, _pB, _pC, _pubSignals } = witness; return encodeAbiParameters( [ { type: 'uint256[2]' }, { type: 'uint256[2][2]' }, { type: 'uint256[2]' }, { type: 'uint256[38]' }, ], [_pA, _pB, _pC, _pubSignals] ); };