Skip to content

Commit

Permalink
Improved comments and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
robknight committed Sep 17, 2024
1 parent ba07b88 commit bf4764c
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 7 deletions.
3 changes: 0 additions & 3 deletions packages/client-rpc/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ export type ParcnetRPCMethodName =
| `identity.${keyof typeof ParcnetRPCSchema.identity}`;

export const RPCMessageSchema = v.union([
/**
* Schema which matches the type of {@link }
*/
v.object({
type: v.literal(RPCMessageType.PARCNET_CLIENT_INVOKE),
serial: v.number(),
Expand Down
28 changes: 28 additions & 0 deletions packages/podspec/test/podspec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const GPC_NPM_ARTIFACTS_PATH = path.join(

describe("podspec should work", function () {
it("should validate POD entries", () => {
// To begin with, we can create a specification for POD entries.
// This is useful for validating POD entries before signing them.
const entriesSpec = p.entries({
firstName: {
type: "string",
Expand All @@ -41,8 +43,10 @@ describe("podspec should work", function () {
}
});

// Generate a random valid public key
const { publicKey } = generateKeyPair();

// Parse some POD entries
const result = entriesSpec.safeParse({
firstName: {
type: "string",
Expand All @@ -62,6 +66,7 @@ describe("podspec should work", function () {
}
});

// The result should be valid
assert(result.isValid);
expect(result.value.firstName).to.eql({ type: "string", value: "test" });
expect(result.value.age).to.eql({ type: "int", value: 41n });
Expand Down Expand Up @@ -160,11 +165,16 @@ describe("podspec should work", function () {
expect(() =>
p.entries({
foo: { type: "string" },
// This is simply invalid data, which would normally cause a type error
// in TypeScript, but such errors can be overridden and would never
// occur in JavaScript.
bar: { type: "invalid" } as never
})
).to.throw();
});

// Integer and Cryptographic entries can be checked to ensure that their
// values are within certain bounds.
it("should apply range checks", function () {
const myPodSpec = p.entries({
foo: { type: "int", inRange: { min: 1n, max: 10n } }
Expand All @@ -186,6 +196,8 @@ describe("podspec should work", function () {
]);
});

// All entries can be checked to ensure that their values are members of
// a list.
it("should test string entries for list membership", function () {
const myPodSpec = p.entries({
foo: {
Expand Down Expand Up @@ -215,6 +227,8 @@ describe("podspec should work", function () {
]);
});

// All entries can be checked to ensure that their values are members of
// a list.
it("should test integer entries for list membership", function () {
const myPodSpec = p.entries({
foo: { type: "int", isMemberOf: $i([1n, 2n, 3n]) }
Expand Down Expand Up @@ -247,15 +261,19 @@ describe("podspec should work", function () {
]);
});

// Tuples are used to match on multiple entries at once.
it("should match on tuples", function () {
const myPodSpec = p.entries({
foo: { type: "string" },
bar: { type: "int" }
});

// Define a tuple of valid entries
const tuples: EntriesTupleSchema<p.InferEntriesType<typeof myPodSpec>>[] = [
{
// The entries to be checked
entries: ["foo", "bar"],
// The list of valid tuples
isMemberOf: [
[
{ type: "string", value: "test" },
Expand Down Expand Up @@ -333,6 +351,8 @@ describe("podspec should work", function () {
}
});

// Optional entries are those which may or may not be present in a POD.
// If present, the entry's "innerType" is used for validation.
it("should handle optional entries", function () {
const optionalPodSpec = p.entries({
foo: { type: "string" },
Expand All @@ -351,6 +371,7 @@ describe("podspec should work", function () {
expect(resultWithoutOptional.isValid).to.eq(true);
});

// In addition to validating sets of entries, we can validate existing PODs.
it("should validate entire PODs", function () {
const { privateKey } = generateKeyPair();
const eventId = "d1390b7b-4ccb-42bf-8c8b-e397b7c26e6c";
Expand All @@ -376,6 +397,8 @@ describe("podspec should work", function () {
assert(result.isValid);
});

// Tuple checks on PODs include a special virtual entry representing the
// POD's signer.
it("should perform tuple checks on PODs including virtual signer entry", function () {
const { publicKey, privateKey } = generateKeyPair();
const eventId = "d1390b7b-4ccb-42bf-8c8b-e397b7c26e6c";
Expand Down Expand Up @@ -431,6 +454,8 @@ describe("podspec should work", function () {
}
});

// Queries can be performed across multiple PODs to find those which match
// a given PODSpec.
it("should query across multiple PODs", function () {
const key = generateRandomHex(32);

Expand Down Expand Up @@ -463,6 +488,7 @@ describe("podspec should work", function () {
expect(queryResult.matchingIndexes).to.eql([1]);
});

// Range checks can also be applied in queries.
it("should apply range checks in queries", function () {
const key = generateRandomHex(32);
const myPodSpec = p.pod({
Expand All @@ -484,6 +510,7 @@ describe("podspec should work", function () {
expect(queryResult.matchingIndexes).to.eql([0, 3]);
});

// Tuple checks also work in queries.
it("should match on tuples in queries", function () {
const { publicKey, privateKey } = generateKeyPair();
const eventId = "d1390b7b-4ccb-42bf-8c8b-e397b7c26e6c";
Expand Down Expand Up @@ -531,6 +558,7 @@ describe("podspec should work", function () {
expect(queryResult.matchingIndexes).to.eql([0]);
});

// Queries can also be used to match on signatures.
it("can query for PODs with matching signatures", function () {
const { publicKey, privateKey } = generateKeyPair();
const eventId = "d1390b7b-4ccb-42bf-8c8b-e397b7c26e6c";
Expand Down
37 changes: 33 additions & 4 deletions packages/podspec/test/ticket-example.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { $s } from "../src/pod_value_utils.js";
import { GPC_NPM_ARTIFACTS_PATH } from "./podspec.spec.js";
import { generateKeyPair } from "./utils.js";

/**
* A specification for tickets, based on the existing example in the Zupass codebase.
*/
const TicketEntries = p.entries({
ticketId: { type: "string" },
eventId: { type: "string" },
Expand All @@ -28,13 +31,16 @@ const TicketEntries = p.entries({
attendeeEmail: { type: "string" }
});

// An event ID for an event
const MY_EVENT_ID = uuidv4();
// Possible product IDs for the event
const MY_EVENT_PRODUCT_IDS = {
VISITOR: uuidv4(),
RESIDENT: uuidv4(),
ORGANIZER: uuidv4()
};

// Example data for a ticket
const VALID_TICKET_DATA = {
ticketId: uuidv4(),
eventId: MY_EVENT_ID,
Expand All @@ -56,11 +62,13 @@ const VALID_TICKET_DATA = {
};

describe.concurrent("podspec ticket example", function () {
// Given a ticket with valid data, it should be recognized as valid
it("should validate ticket entries", async function () {
const result = TicketEntries.safeParse(VALID_TICKET_DATA, { coerce: true });
expect(result.isValid).toBe(true);
});

// Validation of a POD should work
it("should validate ticket PODs", async function () {
const { privateKey } = generateKeyPair();
const entries = TicketEntries.parse(VALID_TICKET_DATA, { coerce: true });
Expand All @@ -73,15 +81,19 @@ describe.concurrent("podspec ticket example", function () {
assert(result.isValid);
});

// Given that we have a schema for tickets, we should be able to create
// narrower schemas for specific events or products, re-using our existing
// specification.
it("should support narrowing of ticket criteria", async function () {
const baseTicketSchema = TicketEntries.schema;

// Override the event ID to be specific to our event
const EventSpecificTicketEntries = p.entries({
...baseTicketSchema,
eventId: { type: "string", isMemberOf: $s([MY_EVENT_ID]) }
eventId: { ...baseTicketSchema.eventId, isMemberOf: $s([MY_EVENT_ID]) }
});

// This will be true because the ticket is for the specified event
// This will succeed because the ticket is for the specified event
const result = EventSpecificTicketEntries.safeParse(VALID_TICKET_DATA, {
coerce: true
});
Expand All @@ -92,6 +104,8 @@ describe.concurrent("podspec ticket example", function () {
it("should reject tickets which do not meet criteria", async function () {
const baseTicketSchema = TicketEntries.schema;

// Now let's create a specification for a ticket which only matches tickets
// with two specific product IDs
const EventAndProductSpecificTicketEntries = p.entries({
...baseTicketSchema,
eventId: { ...baseTicketSchema.eventId, isMemberOf: $s([MY_EVENT_ID]) },
Expand All @@ -105,7 +119,8 @@ describe.concurrent("podspec ticket example", function () {
}
});

// This will be true because the ticket is for the specified event
// This will fail because the ticket is of type "VISITOR", which is not in
// the list of allowed product IDs
const result = EventAndProductSpecificTicketEntries.safeParse(
VALID_TICKET_DATA,
{
Expand All @@ -119,6 +134,8 @@ describe.concurrent("podspec ticket example", function () {
expect(result.issues[0]?.path).toEqual(["productId"]);
});

// Given a collection of tickets, we should be able to query for tickets which
// match a given criteria
it("should be able to find matching tickets from a collection", async function () {
const { privateKey } = generateKeyPair();
const entries = TicketEntries.parse(VALID_TICKET_DATA, { coerce: true });
Expand Down Expand Up @@ -230,6 +247,7 @@ describe.concurrent("podspec ticket example", function () {

const identity = new Identity();

// Turn our JavaScript data into PODEntries
const ticketEntries = TicketEntries.parse(
{
...VALID_TICKET_DATA,
Expand All @@ -238,17 +256,20 @@ describe.concurrent("podspec ticket example", function () {
{ coerce: true }
);

// Create the POD
const ticketPod = POD.sign(ticketEntries, generateKeyPair().privateKey);

const proofRequestSpec = p.proofRequest({
pods: {
// Create proof config for the ticket POD, specific to our event
ticketPod: eventTicketsPodSpec.proofConfig({
revealed: { ticketId: true },
owner: { entry: "attendeeSemaphoreId", protocol: "SemaphoreV3" }
})
}
});

// Get a proof request
const req = proofRequestSpec.getProofRequest();

// There's a membership list check on event ID, so the proof request should
Expand Down Expand Up @@ -278,7 +299,7 @@ describe.concurrent("podspec ticket example", function () {
Object.keys(req.proofConfig.pods.ticketPod?.entries ?? {})
).toHaveLength(2);

await gpcProve(
const result = await gpcProve(
req.proofConfig,
{
pods: {
Expand All @@ -292,5 +313,13 @@ describe.concurrent("podspec ticket example", function () {
},
GPC_NPM_ARTIFACTS_PATH
);

expect(result).toBeDefined();
expect(result.proof).toBeDefined();
expect(result.boundConfig).toBeDefined();
expect(result.revealedClaims).toBeDefined();
expect(
result.revealedClaims.pods.ticketPod?.entries?.ticketId
).toBeDefined();
});
});

0 comments on commit bf4764c

Please sign in to comment.