Creating and Verifying Pods
CCP have provided an example in the form of Creating and Verifying PODs, the below is a slight refinement of these examples I used to understand what was going on.
Prerequisites
You will need to have setup your tools in line with Setting Up Your Tools from CCP, specifically you will need the following:
node v18
(earlier and later versions are not supported)pnpm v8
orv9
(later versions are likely to work but are untested)
Check your versions with:
> node --version
< v18.20.8
> pnpm --version
< 9.15.9
Step 1: Setup the Project
First create a folder, for example ~/source/pod-examples
, from your shell:
mkdir -p ~/source/pod-examples
cd ~/source/pod-examples
Install `@pcd/pod’ and TypeScript:
pnpm install @pcd/pod tsx
Step 2: Generate a Key Pair
Create a file named keygen.ts
and add the following at the top:
// Import Packages
import { randomBytes } from 'crypto';
import { deriveSignerPublicKey } from '@pcd/pod';
import * as path from 'path';
import * as fs from 'fs';
This imports four libraries including a Cryptographically Secure Pseudo Random Number Generator, a function from the PODs SDK to derive a keypair and then two libraries for accessing the file system.
In the same file, add code to generate some random bytes using the CSPRNG this is effectively a huge number in the order of 2^256:
// Generate a 32-byte random private key
const key: Buffer = randomBytes(32);
We need to derive a private key from this huge number, which we do by creating a private signing key:
// Convert the key to a hex string
const privateSigningKey: string = key.toString('hex');
console.log("Generated Private Key:");
console.log(privateSigningKey);
Important
Any key that is generated for production must be appropriately secured, either by deriving it from an otherwise secure form of cryptographic randomness, or by deriving the random bytes from a recovery phrase or other form of cryptographic data. Alternatively the private key can be stored encrypted with a password in a database.
and the public signing key:
// Output Signer Public Key
const publicSigningKey: string = deriveSignerPublicKey(privateSigningKey);
console.log("\nGenerated Signer Public Key:");
console.log(publicSigningKey);
We can then then write these out to files and the console, so that we can use them to create and subsequently verify the POD:
// Get the current directory
const currentDirectory: string = process.cwd();
const privateKeyPath: string = path.join(currentDirectory, "private.key");
const publicKeyPath: string = path.join(currentDirectory, "public.key");
fs.writeFileSync(privateKeyPath, privateSigningKey, 'utf8');
fs.writeFileSync(publicKeyPath, publicSigningKey, 'utf8');
Save the changes to the file, and then run this keygen command with:
pnpm tsx keygen.ts
Step 3: Create the POD
Create a new file named createPod.ts
and add the following imports at the top, again we are pulling in the POD SDK and the utilities to read and write files:
// Import Packages
import { POD, PODEntries, JSONPOD, deriveSignerPublicKey } from "@pcd/pod";
import * as fs from 'fs';
import * as path from 'path';
Now comes the interesting bit, we create the POD itself:
const myEntries: PODEntries = {
security_level: {
type: "int",
value: 4n
},
holder_smart_character_address: {
type: "string",
value: "0x6d11ac8f376b6284a7e5d62a340f71869b3063ae"
},
issued_date: {
type: "date",
value: new Date("2025-04-10T00:00:00.000Z")
},
expiry_date: {
type: "date",
value: new Date("2026-04-10T00:00:00.000Z")
},
pod_type: { type: "string", value: "corpName.security_badge" },
};
This creates an object as follows:
security_level
which is an integer (i.e. a whole number) with a value of4
.holder_smart_character_address
which is a string and represents the public key / address of the holder.issued_date
which is a date expressed as an ISO 8601 format representing the time the POD was created.expiry_data
which is a date expressed as an ISO 8601 format representing the point in time when this pod becomes invalid.pod_type
is astring
containing a namespaced typeId, as this is just a string it could be anything but typically forms a dotted hierarchy from left to right.
Next we do some work to load the key pair from disk:
// Get the current directory and locate, then load, the private key
const currentDirectory: string = process.cwd();
const privateKeyPath: string = path.join(currentDirectory, "private.key");
const privateSigningKey: string = fs.readFileSync(privateKeyPath, 'utf8');
Then we print the public signing key:
// Output Signer Public Key
const publicSigningKey = deriveSignerPublicKey(privateSigningKey);
console.log("\nSigner Public Key");
console.log(publicSigningKey + "\n");
Note
The step above is largely redundant as we already know this public key from the keygen.ts
step; it is however useful to have this printed here as a validation and debugging step as an unexpected key would break the verification step below.
Before we create the POD itself:
// Create the POD
const myPOD = POD.sign(myEntries, privateSigningKey);
And serialize it to a JSON string:
// Convert POD to JSON then String
const jsonPOD: JSONPOD = myPOD.toJSON();
const serializedPOD: string = JSON.stringify(jsonPOD);
We then print the values to the console and write the pod to pod.json
:
// Output POD
console.log(jsonPOD);
console.log("\nStringified\n");
console.log(serializedPOD);
const serializedPODPath = path.join(currentDirectory, "pod.json");
fs.writeFileSync(serializedPODPath, serializedPOD);
Save the changes to the file and run it with:
pnpm tsx createPod.ts
Step 4: Verify the POD
To verify the POD you will need to create a third file verifyPod.ts
and again import some functions from the POD SDK and the fs
and path
libraries:
// Import Packages
import { POD, PODValue } from "@pcd/pod";
import * as fs from 'fs';
import * as path from 'path';
Do the work to load the POD and the public key from the files:
// Load the POD and Public Key
const currentDirectory: string = process.cwd();
const serializedPODPath: string = path.join(currentDirectory, "pod.json");
const serializedPOD: string = fs.readFileSync(serializedPODPath, 'utf8');
const publicKeyPath: string = path.join(currentDirectory, "public.key");
const officialPublicKey: string = fs.readFileSync(publicKeyPath, 'utf8');
You can then parse the POD and load it into the SDK to get a POD in-memory:
// Create the POD from the String
const receivedPOD: POD = POD.fromJSON(JSON.parse(serializedPOD));
The POD
type has several utility functions on it allowing for POD operations, for example .verifySignature()
can be used to verify the POD:
// Verify the POD
if(!receivedPOD.verifySignature()){
throw new Error("Invalid POD");
}
console.log("Verified POD")
By checking the signerPublicKey
from a known trusted source you can validate that it was signed by a trusted party:
if(receivedPOD.signerPublicKey != officialPublicKey){
throw new Error("Not the official signer");
}
console.log("Verified Official Signer")
In the same spirit of caution and trust, you can compare the podType
(or any other known constant values) to ensure that they match the expected type:
const officialPodType: string = "corpName.security_badge"
const podType: PODValue | undefined = receivedPOD.content.getValue("pod_type");
if(podType?.value != officialPodType){
throw new Error("Not the official pod type");
}
console.log("Verified Official Pod Type")
Finally extract the security_level
value from the POD and echo it to the console:
// Get a value from the POD
const level: PODValue | undefined = receivedPOD.content.getValue("security_level");
console.log("Level:", level?.value)
Save the changes and the file and run it with:
pnpm tsx verifyPod.ts
Summary
In this article we have used TypeScript to:
- ✅ Generate a public/private key pair,
- ✅ Create a POD and sign it with the private key,
- ✅ Verify the same POD with the public key.
I’m going to be using this as a Kata to help me better understand the process by repeating the exercise every couple of days. This will helm me develop more familiarity with POD and it’s SDKs and help me build a new mental model, why don’t you join me in this practice by re-implementing it yourself tomorrow.