Skip to content

Commit

Permalink
Refactor Podspec to make it easier to create proof requests
Browse files Browse the repository at this point in the history
  • Loading branch information
robknight committed Sep 17, 2024
1 parent 5f51ca6 commit 9ff6dda
Show file tree
Hide file tree
Showing 26 changed files with 1,087 additions and 265 deletions.
4 changes: 2 additions & 2 deletions apps/client-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"@parcnet-js/client-helpers": "workspace:*",
"@parcnet-js/client-rpc": "workspace:*",
"@parcnet-js/podspec": "workspace:*",
"@pcd/gpc": "0.0.6",
"@pcd/pod": "0.1.5",
"@pcd/gpc": "0.0.8",
"@pcd/pod": "0.1.7",
"@pcd/proto-pod-gpc-artifacts": "^0.5.0",
"@semaphore-protocol/identity": "^4.0.3",
"eventemitter3": "^5.0.1",
Expand Down
10 changes: 5 additions & 5 deletions apps/client-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { listen } from "@parcnet-js/client-helpers/connection/iframe";
import type { Zapp } from "@parcnet-js/client-rpc";
import type { EntriesSchema, PODSchema } from "@parcnet-js/podspec";
import type { EntriesSchema, ProofConfigPODSchema } from "@parcnet-js/podspec";
import { proofRequest } from "@parcnet-js/podspec";
import { gpcProve } from "@pcd/gpc";
import type { POD } from "@pcd/pod";
Expand Down Expand Up @@ -106,23 +106,23 @@ function ProvePODInfo({
onChange
}: {
name: string;
schema: PODSchema<EntriesSchema>;
schema: ProofConfigPODSchema<EntriesSchema>;
pods: POD[];
selectedPOD: POD | undefined;
onChange: (pod: POD | undefined) => void;
}): ReactNode {
const revealedEntries = Object.entries(schema.entries)
const revealedEntries = Object.entries(schema.pod.entries)
.map(([name, entry]) => {
if (entry.type === "optional") {
entry = entry.innerType;
}
return [name, entry] as const;
})
.filter(([_, entry]) => entry.isRevealed);
.filter(([name, _entry]) => schema.revealed?.[name] ?? false);

const selectedPODEntries = selectedPOD?.content.asEntries();

const entriesWithConstraints = Object.entries(schema.entries)
const entriesWithConstraints = Object.entries(schema.pod.entries)
.map(([name, entry]) => {
if (entry.type === "optional") {
entry = entry.innerType;
Expand Down
1 change: 0 additions & 1 deletion apps/client-web/src/client/pod_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export class PODCollection {
}

public query<E extends p.EntriesSchema>(query: p.PODSchema<E>): POD[] {
console.log(query);
return p.pod(query).query(this.pods).matches;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@parcnet-js/app-connector": "workspace:*",
"@parcnet-js/podspec": "workspace:*",
"@pcd/pod": "0.1.5",
"@pcd/pod": "0.1.6",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
},
"pnpm": {
"patchedDependencies": {
"@zk-kit/[email protected]": "patches/@[email protected]",
"@brenoroosevelt/toast": "patches/@brenoroosevelt__toast.patch"
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/app-connector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"@brenoroosevelt/toast": "^2.0.3",
"@parcnet-js/client-rpc": "workspace:*",
"@parcnet-js/podspec": "workspace:*",
"@pcd/gpc": "0.0.6",
"@pcd/pod": "0.1.5",
"@pcd/gpc": "^0.0.8",
"@pcd/pod": "^0.1.7",
"eventemitter3": "^5.0.1",
"json-bigint": "^1.0.0",
"valibot": "^0.42.0"
Expand Down
8 changes: 3 additions & 5 deletions packages/app-connector/src/api_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,16 @@ class ParcnetGPCWrapper {

// In a world with POD2, we would use new POD2 types rather than GPCPCD.
// The existing args system and GPC wrapper works well, so we can use that.
async prove<P extends Record<string, object>>(
args: PodspecProofRequest<P>
): Promise<ProveResult> {
async prove(args: PodspecProofRequest): Promise<ProveResult> {
const result = await this.#api.gpc.prove(args);
return result;
}

async verify<P extends Record<string, object>>(
async verify(
proof: GPCProof,
config: GPCBoundConfig,
revealedClaims: GPCRevealedClaims,
proofRequest: PodspecProofRequest<P>
proofRequest: PodspecProofRequest
): Promise<boolean> {
return this.#api.gpc.verify(proof, config, revealedClaims, proofRequest);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/client-rpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"files": ["dist", "LICENSE"],
"dependencies": {
"@parcnet-js/podspec": "workspace:*",
"@pcd/gpc": "0.0.6",
"@pcd/pod": "0.1.5",
"@pcd/gpc": "^0.0.8",
"@pcd/pod": "^0.1.7",
"valibot": "^0.42.0"
},
"devDependencies": {
Expand Down
13 changes: 12 additions & 1 deletion packages/client-rpc/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,19 @@ const PODSchemaSchema = v.object({
signature: v.optional(SignatureSchema)
});

const ProofConfigPODSchemaSchema = v.object({
pod: PODSchemaSchema,
revealed: v.optional(v.record(v.string(), v.optional(v.boolean()))),
owner: v.optional(
v.object({
entry: v.string(),
protocol: v.union([v.literal("SemaphoreV3"), v.literal("SemaphoreV4")])
})
)
});

const PodspecProofRequestSchema = v.object({
pods: v.record(v.string(), PODSchemaSchema),
pods: v.record(v.string(), ProofConfigPODSchemaSchema),
externalNullifier: v.optional(PODValueSchema),
watermark: v.optional(PODValueSchema)
});
Expand Down
37 changes: 31 additions & 6 deletions packages/podspec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
"module": "dist/index.js",
"type": "module",
"types": "src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./pod_value_utils": {
"types": "./src/pod_value_utils.ts",
"import": "./dist/pod_value_utils.js",
"require": "./dist/pod_value_utils.cjs"
}
},
"scripts": {
"lint": "eslint . --max-warnings 0",
"build": "tsup 'src/**/*@(ts|tsx)' --format cjs,esm --clean --sourcemap",
Expand All @@ -14,22 +26,35 @@
},
"files": ["dist", "./README.md", "./LICENSE"],
"dependencies": {
"@pcd/gpc": "0.0.6",
"@pcd/pod": "0.1.5"
"@pcd/gpc": "0.0.8",
"@pcd/pod": "0.1.7"
},
"devDependencies": {
"@parcnet-js/eslint-config": "workspace:*",
"@parcnet-js/typescript-config": "workspace:*",
"@pcd/proto-pod-gpc-artifacts": "^0.5.0",
"@pcd/proto-pod-gpc-artifacts": "^0.9.0",
"@semaphore-protocol/identity": "^3.15.2",
"@types/uuid": "^9.0.0",
"@zk-kit/eddsa-poseidon": "1.0.2",
"@zk-kit/eddsa-poseidon": "1.0.3",
"tsup": "^8.2.4",
"typescript": "^5.5",
"uuid": "^9.0.0",
"vitest": "^2.0.5"
"vitest": "^2.1.1"
},
"publishConfig": {
"access": "public",
"types": "dist/index.d.ts"
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./pod_value_utils": {
"types": "./dist/pod_value_utils.d.ts",
"import": "./dist/pod_value_utils.js",
"require": "./dist/pod_value_utils.cjs"
}
}
}
}
102 changes: 62 additions & 40 deletions packages/podspec/src/gpc/proof_request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
GPCProofEntryConfig,
GPCProofObjectConfig,
GPCProofTupleConfig,
IdentityProtocol,
PODEntryIdentifier,
PODMembershipLists
} from "@pcd/gpc";
Expand All @@ -11,7 +12,21 @@ import { PodSpec } from "../parse/pod.js";
import type { EntriesSchema } from "../schemas/entries.js";
import type { PODSchema } from "../schemas/pod.js";

type Pods = Record<string, object>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type NamedPODs = Record<string, ProofConfigPODSchema<any>>;

export interface ProofConfigOwner<E extends EntriesSchema> {
entry: Extract<keyof E, string>;
protocol: IdentityProtocol;
}

export interface ProofConfigPODSchema<E extends EntriesSchema> {
pod: PODSchema<E>;
revealed?: Partial<{
[K in Extract<keyof (E & { $signerPublicKey: never }), string>]: boolean;
}>;
owner?: ProofConfigOwner<E>;
}

/**
* A ProofRequest contains the data necessary to verify that a given GPC proof
Expand All @@ -28,19 +43,8 @@ export type ProofRequest = {
* A PodspecProofRequest allows us to generate a {@link ProofRequest} from a
* set of Podspecs defining the allowable PODs.
*/
export interface PodspecProofRequest<
P extends Record<string, object> = Record<string, PODSchema<EntriesSchema>>
> {
pods: Readonly<{
[K in keyof P]: P[K] extends PODSchema<infer T>
? P[K] & PODSchema<T>
: never;
}>;
inputPods?: Readonly<{
[K in keyof P]: P[K] extends PODSchema<infer T>
? P[K] & PODSchema<T>
: never;
}>;
export interface PodspecProofRequestSchema<P extends NamedPODs = NamedPODs> {
pods: P;
externalNullifier?: PODValue;
watermark?: PODValue;
}
Expand All @@ -50,23 +54,24 @@ export interface PodspecProofRequest<
* set of Podspecs defining the allowable PODs.
*/
export class ProofRequestSpec<
P extends PodspecProofRequest<T>,
T extends Pods
P extends PodspecProofRequestSchema<T>,
T extends NamedPODs
> {
/**
* Private constructor, see {@link create}.
* @param schema The schema of the PODs that are allowed in this proof.
*/
private constructor(public readonly schema: PodspecProofRequest<T>) {}
private constructor(public readonly schema: PodspecProofRequestSchema<T>) {}

/**
* Create a new ProofRequestSpec.
* @param schema The schema of the PODs that are allowed in this proof.
* @returns A new ProofRequestSpec.
*/
public static create<P extends PodspecProofRequest<T>, T extends Pods>(
schema: PodspecProofRequest<T>
): ProofRequestSpec<P, T> {
public static create<
P extends PodspecProofRequestSchema<T>,
T extends NamedPODs
>(schema: PodspecProofRequestSchema<T>): ProofRequestSpec<P, T> {
return new ProofRequestSpec(schema);
}

Expand All @@ -92,12 +97,10 @@ export class ProofRequestSpec<
*/
public queryForInputs(pods: POD[]): Record<keyof P["pods"], POD[]> {
const result: Record<string, POD[]> = {};
for (const [podName, podSchema] of Object.entries(
(this.schema.inputPods ?? this.schema.pods) as Record<
string,
PODSchema<EntriesSchema>
>
for (const [podName, proofConfigPODSchema] of Object.entries(
this.schema.pods as Record<string, ProofConfigPODSchema<EntriesSchema>>
)) {
const podSchema = proofConfigPODSchema.pod;
result[podName] = PodSpec.create(podSchema).query(pods).matches;
}
return result as Record<keyof P["pods"], POD[]>;
Expand All @@ -107,44 +110,63 @@ export class ProofRequestSpec<
/**
* Export for convenience.
*/
export const proofRequest = <P extends Pods>(schema: PodspecProofRequest<P>) =>
ProofRequestSpec.create(schema);
export const proofRequest = <P extends NamedPODs>(
schema: PodspecProofRequestSchema<P>
) => ProofRequestSpec.create(schema);

/**
* Generates a {@link ProofRequest}.
*
* @param request The PodspecProofRequest to derive the ProofRequest from.
* @returns A ProofRequest.
*/
function makeProofRequest<P extends Pods>(
request: PodspecProofRequest<P>
function makeProofRequest<P extends NamedPODs>(
request: PodspecProofRequestSchema<P>
): ProofRequest {
const pods: Record<PODName, GPCProofObjectConfig> = {};
const membershipLists: PODMembershipLists = {};
const tuples: Record<PODName, GPCProofTupleConfig> = {};

for (const [podName, podSchema] of Object.entries(
request.pods as Record<string, PODSchema<EntriesSchema>>
for (const [podName, proofConfigPODSchema] of Object.entries(
request.pods as Record<string, ProofConfigPODSchema<EntriesSchema>>
)) {
const podConfig: GPCProofObjectConfig = { entries: {} };
const podSchema = proofConfigPODSchema.pod;
const owner = proofConfigPODSchema.owner;

for (const [entryName, schema] of Object.entries(podSchema.entries)) {
const entrySchema =
schema.type === "optional" ? schema.innerType : schema;

const isRevealed = proofConfigPODSchema.revealed?.[entryName] ?? false;
const isMemberOf = entrySchema.isMemberOf;
const isNotMemberOf = entrySchema.isNotMemberOf;
const inRange =
(entrySchema.type === "cryptographic" || entrySchema.type === "int") &&
entrySchema.inRange;
const isOwnerID =
entrySchema.type === "cryptographic" && owner?.entry === entryName;

if (
!isRevealed &&
!isMemberOf &&
!isNotMemberOf &&
!inRange &&
!isOwnerID
) {
continue;
}

const entryConfig: GPCProofEntryConfig = {
isRevealed: entrySchema.isRevealed ?? false,
isMemberOf: entrySchema.isMemberOf
isRevealed,
isMemberOf: isMemberOf
? `allowlist_${podName}_${entryName}`
: undefined,
isNotMemberOf: entrySchema.isNotMemberOf
isNotMemberOf: isNotMemberOf
? `blocklist_${podName}_${entryName}`
: undefined,
...(entrySchema.type === "cryptographic" || entrySchema.type === "int"
? { inRange: entrySchema.inRange }
: {}),
...(entrySchema.type === "cryptographic" && entrySchema.isOwnerID
? { isOwnerID: true }
: {})
...(inRange ? { inRange: entrySchema.inRange } : {}),
...(isOwnerID ? { isOwnerID: owner.protocol } : {})
};
podConfig.entries[entryName] = entryConfig;

Expand Down
Loading

0 comments on commit 9ff6dda

Please sign in to comment.