@antefinance/ante-sdk
Version:
Library for interacting with Ante smart contracts
311 lines (276 loc) • 8.43 kB
text/typescript
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]
);
};