diff --git a/apps/passport-client/components/shared/PCDCard.tsx b/apps/passport-client/components/shared/PCDCard.tsx index 5a5773bd45..22341afca0 100644 --- a/apps/passport-client/components/shared/PCDCard.tsx +++ b/apps/passport-client/components/shared/PCDCard.tsx @@ -210,7 +210,9 @@ export const TicketQRWrapper = forwardRef< ); } if (isPODTicketPCD(pcd)) { - const urls = getURLsBasedOnCategory(pcd.claim.ticket.ticketCategory); + const urls = getURLsBasedOnCategory( + pcd.claim.ticket.ticketCategory ?? TicketCategory.Generic + ); if (urls.idBasedVerifyURL) return ( diff --git a/apps/passport-server/src/services/generic-issuance/pipelines/CSVPipeline/makeTicketPCD.ts b/apps/passport-server/src/services/generic-issuance/pipelines/CSVPipeline/makeTicketPCD.ts index 520639c0f0..bc8417d3fd 100644 --- a/apps/passport-server/src/services/generic-issuance/pipelines/CSVPipeline/makeTicketPCD.ts +++ b/apps/passport-server/src/services/generic-issuance/pipelines/CSVPipeline/makeTicketPCD.ts @@ -111,14 +111,14 @@ export function csvRowToPODTicketData( ticketId, // The ticket ID is a unique identifier of the ticket. eventId, // The event ID uniquely identifies an event. productId, // The product ID uniquely identifies the type of ticket (e.g. General Admission, Volunteer etc.). - timestampConsumed: 0, // change if checkin feature enabled for csv pipelines + timestampConsumed: undefined, // change if checkin feature enabled for csv pipelines timestampSigned: Date.now(), attendeeSemaphoreId, ticketSecret: undefined, owner, - isConsumed: false, // changes if checkin feature enabled for csv pipelines - isRevoked: false, - ticketCategory: TicketCategory.Generic, + isConsumed: false, // change if checkin feature enabled for csv pipelines + isRevoked: undefined, // change if revocation feature changes to not just delete tickets + ticketCategory: undefined, // change if we ever support multiple categories for csv pipelines attendeeName, attendeeEmail } satisfies IPODTicketData; diff --git a/apps/passport-server/src/services/generic-issuance/pipelines/CSVTicketPipeline/CSVTicketPipeline.ts b/apps/passport-server/src/services/generic-issuance/pipelines/CSVTicketPipeline/CSVTicketPipeline.ts index e15caa2288..c57c2cb3c6 100644 --- a/apps/passport-server/src/services/generic-issuance/pipelines/CSVTicketPipeline/CSVTicketPipeline.ts +++ b/apps/passport-server/src/services/generic-issuance/pipelines/CSVTicketPipeline/CSVTicketPipeline.ts @@ -364,11 +364,11 @@ export class CSVTicketPipeline implements BasePipeline { ...atom, owner: semaphoreV4Id, eventName: this.definition.options.eventName, - ticketCategory: TicketCategory.Generic, - timestampConsumed: 0, + //ticketCategory: TicketCategory.Generic, + //timestampConsumed: 0, timestampSigned: new Date().getTime(), - isConsumed: false, - isRevoked: false + isConsumed: false + //isRevoked: false }; const pcd = await PODTicketPCDPackage.prove({ diff --git a/apps/passport-server/src/services/generic-issuance/pipelines/PretixPipeline.ts b/apps/passport-server/src/services/generic-issuance/pipelines/PretixPipeline.ts index df5cd96988..8f6840cde8 100644 --- a/apps/passport-server/src/services/generic-issuance/pipelines/PretixPipeline.ts +++ b/apps/passport-server/src/services/generic-issuance/pipelines/PretixPipeline.ts @@ -1018,6 +1018,9 @@ export class PretixPipeline implements BasePipeline { manualTicket.id ); + // Commented-out fields below are optional, unneded for current use cases, + // and omitted to keep the ticket POD below 16 entries (Merkle depth 5 + // in GPC proofs). return { ticketId: manualTicket.id, eventId: manualTicket.eventId, @@ -1030,10 +1033,10 @@ export class PretixPipeline implements BasePipeline { eventLocation: event.imageOptions?.eventLocation, isAddOn: product.isAddOnItem, isConsumed: checkIn ? true : false, - isRevoked: false, + // isRevoked: false, timestampSigned: Date.now(), - timestampConsumed: checkIn ? checkIn.timestamp.getTime() : 0, - ticketCategory: TicketCategory.Generic, + // timestampConsumed: checkIn ? checkIn.timestamp.getTime() : 0, + // ticketCategory: TicketCategory.Generic, eventName: event.name, ticketName: product.name, checkerEmail: undefined, @@ -1273,17 +1276,20 @@ export class PretixPipeline implements BasePipeline { throw new Error(`Atom missing email: ${atom.id} in pipeline ${this.id}`); } + // Commented-out fields below are optional, unneded for current use cases, + // and omitted to keep the ticket POD below 16 entries (Merkle depth 5 + // in GPC proofs). return { attendeeName: atom.name, attendeeEmail: atom.email, eventName: this.atomToEventName(atom), ticketName: this.atomToTicketName(atom), - checkerEmail: undefined, + //checkerEmail: undefined, ticketSecret: atom.secret, ticketId: atom.id, eventId: atom.eventId, productId: atom.productId, - timestampConsumed: atom.timestampConsumed?.getTime() ?? 0, + //timestampConsumed: atom.timestampConsumed?.getTime() ?? 0, timestampSigned: Date.now(), owner: semaphoreV4Id, imageUrl: this.atomToImageUrl(atom), @@ -1292,8 +1298,8 @@ export class PretixPipeline implements BasePipeline { eventLocation: this.atomToEventLocation(atom), isAddOn: !!atom.parentAtomId, isConsumed: atom.isConsumed, - isRevoked: false, - ticketCategory: TicketCategory.Generic, + //isRevoked: false, + //ticketCategory: TicketCategory.Generic, parentTicketId: atom.parentAtomId ?? undefined }; } diff --git a/apps/passport-server/test/generic-issuance/pipelines/pretix/pretixPipeline-with-v4.spec.ts b/apps/passport-server/test/generic-issuance/pipelines/pretix/pretixPipeline-with-v4.spec.ts index 0fc991b0fb..e0afa60ac7 100644 --- a/apps/passport-server/test/generic-issuance/pipelines/pretix/pretixPipeline-with-v4.spec.ts +++ b/apps/passport-server/test/generic-issuance/pipelines/pretix/pretixPipeline-with-v4.spec.ts @@ -420,9 +420,8 @@ describe("generic issuance - PretixPipeline with semaphore v4 enabled", function expect(ManualAttendeePODTicket.claim.ticket.imageUrl).to.eq( EthLatAmImageUrl ); - expect(ManualAttendeePODTicket.claim.ticket.timestampConsumed).to.eq( - Date.now() - ); + expect(ManualAttendeePODTicket.claim.ticket.timestampConsumed).to.be + .undefined; } const manualBouncerChecksInManualAttendeeAgain = diff --git a/examples/test-zapp/src/apis/GPC.tsx b/examples/test-zapp/src/apis/GPC.tsx index 938250e0be..8b9ccc300c 100644 --- a/examples/test-zapp/src/apis/GPC.tsx +++ b/examples/test-zapp/src/apis/GPC.tsx @@ -201,13 +201,10 @@ const ticketData = { ticketName: "Ticket 1", eventName: "Event 1", ticketSecret: "secret123", - timestampConsumed: 1714857600, timestampSigned: 1714857600, attendeeSemaphoreId: ${identityV3}, owner: "${publicKey}", isConsumed: 0, - isRevoked: 0, - ticketCategory: 0, attendeeName: "John Doe", attendeeEmail: "test@example.com" }; @@ -227,13 +224,13 @@ await z.pod.insert(pod); ticketName: "Ticket 1", eventName: "Event 1", ticketSecret: "secret123", - timestampConsumed: 1714857600, + //timestampConsumed: 1714857600, timestampSigned: 1714857600, attendeeSemaphoreId: identityV3 as bigint, owner: publicKey as string, isConsumed: 0, - isRevoked: 0, - ticketCategory: 0, + //isRevoked: 0, + //ticketCategory: 0, attendeeName: "John Doe", attendeeEmail: "test@example.com" }; diff --git a/packages/pcd/pod-ticket-pcd/src/schema.ts b/packages/pcd/pod-ticket-pcd/src/schema.ts index 736d634934..a89e6a1109 100644 --- a/packages/pcd/pod-ticket-pcd/src/schema.ts +++ b/packages/pcd/pod-ticket-pcd/src/schema.ts @@ -11,7 +11,7 @@ export const TicketDataSchema = z.object({ ticketId: z.string().uuid(), eventId: z.string().uuid(), productId: z.string().uuid(), - timestampConsumed: z.number().int().nonnegative(), + timestampConsumed: z.number().int().nonnegative().optional(), timestampSigned: z.number().int().nonnegative(), /** * V3 semaphore commitment. @@ -35,8 +35,8 @@ export const TicketDataSchema = z.object({ // `dataToPodEntries` will not work .transform(eddsaPublicKey), isConsumed: z.boolean(), - isRevoked: z.boolean(), - ticketCategory: z.nativeEnum(TicketCategory), + isRevoked: z.boolean().optional(), + ticketCategory: z.nativeEnum(TicketCategory).optional(), attendeeName: z.string(), attendeeEmail: z.string(), qrCodeOverrideImageUrl: z.string().optional(), diff --git a/packages/pcd/pod-ticket-pcd/src/utils.ts b/packages/pcd/pod-ticket-pcd/src/utils.ts index f0ae33bbde..adbc5b1d4c 100644 --- a/packages/pcd/pod-ticket-pcd/src/utils.ts +++ b/packages/pcd/pod-ticket-pcd/src/utils.ts @@ -71,7 +71,7 @@ export function dataToPodEntries( // wraps a String is either a String or missing entirely. if (typeName === "ZodOptional") { // If there's no value for this field, don't add an entry for it. - if (!data[key]) { + if (data[key] === undefined) { continue; } else { typeName = field._def.innerType._def.typeName; diff --git a/packages/pcd/pod-ticket-pcd/test/pod-ticket-pcd.spec.ts b/packages/pcd/pod-ticket-pcd/test/pod-ticket-pcd.spec.ts index 31340ebce1..4930eb6f6b 100644 --- a/packages/pcd/pod-ticket-pcd/test/pod-ticket-pcd.spec.ts +++ b/packages/pcd/pod-ticket-pcd/test/pod-ticket-pcd.spec.ts @@ -99,6 +99,50 @@ describe("PODTicketPCD should work", function () { expect(ticketPOD.signerPublicKey).to.eq(expectedPublicKey); }); + it("should be able to create and verify a ticket with minimal entries", async function () { + const noOptionalFieldsTicketData: IPODTicketData = { + attendeeName: "test name", + attendeeEmail: "user@test.com", + eventName: "event", + ticketName: "ticket", + checkerEmail: "checker@test.com", + ticketId: "0450fd86-fa6f-430b-81ac-24b03a75be01", + eventId: "d451327c-9997-449a-a6fb-bea11e816533", + productId: "a7c633a4-618a-474c-bb33-523ba68e6314", + timestampSigned: Date.UTC(2024, 10, 11), + // Owner is optional, but implicitly one of the two types of + // ownership should be present. + owner: "9x0qSqXus/VG4OgfyHWvVEFIiaTa7rsE/kS0YsHNNQI", + isConsumed: false + }; + + const noOptionalFieldsTicket = await PODTicketPCDPackage.prove({ + ticket: { + value: noOptionalFieldsTicketData, + argumentType: ArgumentTypeName.Object + }, + privateKey: { + value: prvKey, + argumentType: ArgumentTypeName.String + }, + id: { + value: COMPAT_TEST_PCD_ID, + argumentType: ArgumentTypeName.String + } + }); + + expect(await PODTicketPCDPackage.verify(noOptionalFieldsTicket)).to.be.true; + expect(noOptionalFieldsTicket.type).to.eq(PODTicketPCDTypeName); + expect(noOptionalFieldsTicket.id).to.eq(COMPAT_TEST_PCD_ID); + + const noOptionalFieldsTicketPOD = ticketToPOD(noOptionalFieldsTicket); + expect(noOptionalFieldsTicketPOD.verifySignature()).to.be.true; + console.error("ART_DBG", noOptionalFieldsTicketPOD.content.asEntries()); + expect( + Object.keys(noOptionalFieldsTicketPOD.content.asEntries()) + ).to.have.length(12); + }); + it("should not be possible to verify a ticket that has been tampered with", async function () { const originalTicketData = ticket.claim.ticket; ticket.claim.ticket = {