Skip to content

Commit

Permalink
Tighten up and document the POD Email PCD type
Browse files Browse the repository at this point in the history
  • Loading branch information
robknight committed Jan 17, 2025
1 parent 88b1b9e commit b993671
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 5 deletions.
7 changes: 6 additions & 1 deletion packages/pcd/pod-email-pcd/src/PODEmailPCD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type PODEmailPCDArgs = {

/**
* The signer's semaphore v4 public key
* @todo link to documentation on public key format
* See {@link @pcd/pod!encodePublicKey} for details of the string format.
*/
semaphoreV4PublicKey: StringArgument;

Expand All @@ -32,9 +32,14 @@ export type PODEmailPCDArgs = {
id: StringArgument;
};

type EmailPODTypeValue = PODStringValue & {
value: "zupass.email";
};

export type PODEmailPCDRequiredEntries = {
emailAddress: PODStringValue;
semaphoreV4PublicKey: PODEdDSAPublicKeyValue;
pod_type: EmailPODTypeValue;
};

export interface PODEmailPCDClaim {
Expand Down
93 changes: 89 additions & 4 deletions packages/pcd/pod-email-pcd/src/PODEmailPCDPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import {
PODEmailPCDTypeName
} from "./PODEmailPCD";

/**
* Loads POD values from a PODEmailPCD.
*
* @param pcd - The PODEmailPCD to load.
* @returns The POD.
*/
function loadPOD(pcd: PODEmailPCD): POD {
return POD.load(
pcd.claim.podEntries,
Expand All @@ -17,6 +23,12 @@ function loadPOD(pcd: PODEmailPCD): POD {
);
}

/**
* Creates a PODEmailPCD from the given arguments.
*
* @param args - The arguments for the PODEmailPCD.
* @returns The PODEmailPCD.
*/
export async function prove(args: PODEmailPCDArgs): Promise<PODEmailPCD> {
if (!args.privateKey.value) {
throw new Error("missing private key");
Expand All @@ -38,11 +50,16 @@ export async function prove(args: PODEmailPCDArgs): Promise<PODEmailPCD> {
semaphoreV4PublicKey: {
type: "eddsa_pubkey",
value: args.semaphoreV4PublicKey.value
},
pod_type: {
type: "string",
value: "zupass.email"
}
};

const pod = POD.sign(podEntries, args.privateKey.value);

// The POD signature is used as the ID if no ID is provided.
const id = args.id.value ?? pod.signature;

return new PODEmailPCD(
Expand All @@ -57,10 +74,31 @@ export async function prove(args: PODEmailPCDArgs): Promise<PODEmailPCD> {
);
}

/**
* Verifies a PODEmailPCD.
*
* This checks that the required entries are present and valid, then verifies
* the POD signature.
*
* @param pcd - The PODEmailPCD to verify.
* @returns Whether the PODEmailPCD is valid.
*/
export async function verify(pcd: PODEmailPCD): Promise<boolean> {
try {
checkPODEntries(pcd.claim.podEntries);
} catch (e) {
return false;
}

return loadPOD(pcd).verifySignature();
}

/**
* Serializes a PODEmailPCD.
*
* @param pcd - The PODEmailPCD to serialize.
* @returns The serialized PODEmailPCD.
*/
export async function serialize(
pcd: PODEmailPCD
): Promise<SerializedPCD<PODEmailPCD>> {
Expand All @@ -73,34 +111,72 @@ export async function serialize(
};
}

/**
* Checks that the required entries are present and valid.
*
* This is used both to check the entries when creating a PODEmailPCD and to
* check the entries when deserializing a PODEmailPCD.
*
* Note that this does not ensure that there are no additional entries. This allows
* for compatibility with future extensions to the POD.
*
* @param podEntries - The POD entries to check.
* @returns Whether the POD entries are valid.
* @throws If the POD entries are invalid.
*/
function checkPODEntries(
podEntries: PODEntries
): podEntries is PODEntries & PODEmailPCDRequiredEntries {
if (!podEntries.emailAddress) {
throw new Error("emailAddress entry is missing");
throw new TypeError("emailAddress entry is missing");
}

if (!podEntries.semaphoreV4PublicKey) {
throw new Error("semaphoreV4PublicKey entry is missing");
throw new TypeError("semaphoreV4PublicKey entry is missing");
}

if (!podEntries.pod_type) {
throw new TypeError("pod_type entry is missing");
}

if (typeof podEntries.emailAddress.value !== "string") {
throw new Error("emailAddress entry is not a string");
throw new TypeError("emailAddress entry is not a string");
}

if (podEntries.semaphoreV4PublicKey.type !== "eddsa_pubkey") {
throw new Error("semaphoreV4PublicKey entry is not an eddsa public key");
throw new TypeError(
"semaphoreV4PublicKey entry is not an eddsa public key"
);
}

if (
podEntries.pod_type.type !== "string" ||
podEntries.pod_type.value !== "zupass.email"
) {
throw new TypeError(
"pod_type entry is not a string with value 'zupass.email'"
);
}

return true;
}

/**
* Deserializes a PODEmailPCD.
*
* @param serialized - The serialized PODEmailPCD.
* @returns The deserialized PODEmailPCD.
* @throws If the deserialized PODEmailPCD is invalid.
*/
export async function deserialize(serialized: string): Promise<PODEmailPCD> {
const wrapper = JSON.parse(serialized);
const pod = POD.fromJSON(wrapper.pod);
const podEntries = pod.content.asEntries();

if (!checkPODEntries(podEntries)) {
// This will never throw because `checkPODEntries` will either throw an
// error or return true. However, it is necessary to include this for
//the type checker.
throw new Error("invalid pod entries");
}

Expand All @@ -114,13 +190,22 @@ export async function deserialize(serialized: string): Promise<PODEmailPCD> {
);
}

/**
* Gets the display options for a PODEmailPCD.
*
* @param pcd - The PODEmailPCD to get the display options for.
* @returns The display options.
*/
export function getDisplayOptions(pcd: PODEmailPCD): DisplayOptions {
return {
header: "Verified Email",
displayName: pcd.claim.podEntries.emailAddress.value
};
}

/**
* The PCD package for PODEmailPCD.
*/
export const PODEmailPCDPackage: PCDPackage<
PODEmailPCDClaim,
PODEmailPCDProof,
Expand Down
7 changes: 7 additions & 0 deletions packages/pcd/pod-email-pcd/test/pod-email-pcd.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,17 @@ describe("POD Email PCD should work", function () {
});

it("should reflect prove args", async function () {
expect(podEmailPCD.claim.podEntries.emailAddress.type).to.eq("string");
expect(podEmailPCD.claim.podEntries.emailAddress.value).to.eq(emailAddress);
expect(podEmailPCD.claim.podEntries.semaphoreV4PublicKey.type).to.eq(
"eddsa_pubkey"
);
expect(podEmailPCD.claim.podEntries.semaphoreV4PublicKey.value).to.eq(
semaphoreV4PublicKey
);

expect(podEmailPCD.claim.podEntries.pod_type.type).to.eq("string");
expect(podEmailPCD.claim.podEntries.pod_type.value).to.eq("zupass.email");
expect(podEmailPCD.id).to.not.be.empty;
});

Expand Down

0 comments on commit b993671

Please sign in to comment.