Skip to content

Commit

Permalink
Refactor verification and add some type tests
Browse files Browse the repository at this point in the history
  • Loading branch information
robknight committed Oct 7, 2024
1 parent 53312cc commit 76806d0
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 141 deletions.
13 changes: 13 additions & 0 deletions apps/client-web/src/client/gpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ export class ParcnetGPCProcessor implements ParcnetGPCRPC {
}

public async verify(
proof: GPCProof,
boundConfig: GPCBoundConfig,
revealedClaims: GPCRevealedClaims
): Promise<boolean> {
return gpcVerify(
proof,
boundConfig,
revealedClaims,
new URL("/artifacts", window.location.origin).toString()
);
}

public async verifyWithProofRequest(
proof: GPCProof,
boundConfig: GPCBoundConfig,
revealedClaims: GPCRevealedClaims,
Expand Down
9 changes: 8 additions & 1 deletion apps/docs/src/content/docs/guides/ticket-proofs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,11 @@ If you add a watermark to your proof request, you can check the watermark when l

# Verifying a ticket proof

TODO.
Once a proof has been made, you can verify it.

Typically, verification is done by a different person, computer, or program than the one that produced the proof. You don't really need to verify a proof that you just created on the same computer, but if you received a proof from someone else, or if you have an application which sends proofs between, say, a client and a server then you will want to verify any proofs that you receive.

Verification covers a few important principles:
- That, given a `revealedClaims` object which makes certain statements, a proof configuration, and a proof object, the proof object really does correspond to the configuration and revealed claims. If we didn't check this, then the `revealedClaims` might contain data for which there is no proof!
- That the configuration really is the one that you expect it to be. This is important because a proof might really match a set of claims and a configuration, but if the configuration is not the one you expect then the claims might not be valid for your use-case.

2 changes: 1 addition & 1 deletion packages/app-connector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"lint": "eslint . --max-warnings 0",
"build": "tsup 'src/**/*@(ts|tsx)' --format cjs,esm --clean --sourcemap",
"prepublish": "tsup 'src/**/*@(ts|tsx)' --format cjs,esm --clean --sourcemap --dts",
"test": "vitest run"
"test": "vitest run --typecheck"
},
"files": ["dist", "LICENSE"],
"dependencies": {
Expand Down
15 changes: 14 additions & 1 deletion packages/app-connector/src/api_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,25 @@ export class ParcnetGPCWrapper {
}

async verify(
proof: GPCProof,
config: GPCBoundConfig,
revealedClaims: GPCRevealedClaims
): Promise<boolean> {
return this.#api.gpc.verify(proof, config, revealedClaims);
}

async verifyWithProofRequest(
proof: GPCProof,
config: GPCBoundConfig,
revealedClaims: GPCRevealedClaims,
proofRequest: p.PodspecProofRequest
): Promise<boolean> {
return this.#api.gpc.verify(proof, config, revealedClaims, proofRequest);
return this.#api.gpc.verifyWithProofRequest(
proof,
config,
revealedClaims,
proofRequest
);
}
}

Expand Down
15 changes: 13 additions & 2 deletions packages/app-connector/src/rpc_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,26 @@ export class ParcnetRPCConnector implements ParcnetRPC, ParcnetEvents {
);
},
verify: async (
proof: GPCProof,
boundConfig: GPCBoundConfig,
revealedClaims: GPCRevealedClaims
): Promise<boolean> => {
return this.#typedInvoke(
"gpc.verify",
[proof, boundConfig, revealedClaims],
ParcnetRPCSchema.gpc.verify
);
},
verifyWithProofRequest: async (
proof: GPCProof,
boundConfig: GPCBoundConfig,
revealedClaims: GPCRevealedClaims,
proofRequest: PodspecProofRequest
): Promise<boolean> => {
return this.#typedInvoke(
"gpc.verify",
"gpc.verifyWithProofRequest",
[proof, boundConfig, revealedClaims, proofRequest],
ParcnetRPCSchema.gpc.verify
ParcnetRPCSchema.gpc.verifyWithProofRequest
);
}
};
Expand Down
7 changes: 5 additions & 2 deletions packages/client-rpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"scripts": {
"lint": "eslint . --max-warnings 0",
"build": "tsup 'src/**/*@(ts|tsx)' --format cjs,esm --clean --sourcemap",
"prepublish": "tsup 'src/**/*@(ts|tsx)' --format cjs,esm --clean --sourcemap --dts"
"prepublish": "tsup 'src/**/*@(ts|tsx)' --format cjs,esm --clean --sourcemap --dts",
"test": "vitest run --typecheck"
},
"files": ["dist", "LICENSE"],
"dependencies": {
Expand All @@ -32,10 +33,12 @@
},
"devDependencies": {
"@parcnet-js/eslint-config": "workspace:*",
"@parcnet-js/podspec": "workspace:*",
"@parcnet-js/typescript-config": "workspace:*",
"@types/snarkjs": "^0.7.8",
"tsup": "^8.2.4",
"typescript": "^5.5"
"typescript": "^5.5",
"vitest": "^2.1.2"
},
"publishConfig": {
"access": "public",
Expand Down
5 changes: 5 additions & 0 deletions packages/client-rpc/src/rpc_interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface ParcnetGPCRPC {
prove: (request: PodspecProofRequest) => Promise<ProveResult>;
canProve: (request: PodspecProofRequest) => Promise<boolean>;
verify: (
proof: GPCProof,
boundConfig: GPCBoundConfig,
revealedClaims: GPCRevealedClaims
) => Promise<boolean>;
verifyWithProofRequest: (
proof: GPCProof,
boundConfig: GPCBoundConfig,
revealedClaims: GPCRevealedClaims,
Expand Down
139 changes: 10 additions & 129 deletions packages/client-rpc/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,134 +1,11 @@
import type { GPCBoundConfig, GPCRevealedClaims } from "@pcd/gpc";
import type { Groth16Proof } from "snarkjs";
import * as v from "valibot";
import type { ParcnetRPC } from "./rpc_interfaces.js";

const PODValueSchema = v.variant("type", [
v.object({
type: v.literal("string"),
value: v.string()
}),
v.object({
type: v.literal("int"),
value: v.bigint()
}),
v.object({
type: v.literal("cryptographic"),
value: v.bigint()
}),
v.object({
type: v.literal("eddsa_pubkey"),
value: v.string()
})
]);

const PODEntriesSchema = v.record(v.string(), PODValueSchema);

const DefinedEntrySchema = v.variant("type", [
v.object({
type: v.literal("string"),
isMemberOf: v.optional(v.array(PODValueSchema)),
isNotMemberOf: v.optional(v.array(PODValueSchema)),
isRevealed: v.optional(v.boolean()),
equalsEntry: v.optional(v.string())
}),
v.object({
type: v.literal("int"),
isMemberOf: v.optional(v.array(PODValueSchema)),
isNotMemberOf: v.optional(v.array(PODValueSchema)),
isRevealed: v.optional(v.boolean()),
equalsEntry: v.optional(v.string()),
inRange: v.optional(
v.object({
min: v.bigint(),
max: v.bigint()
})
)
}),
v.object({
type: v.literal("cryptographic"),
isMemberOf: v.optional(v.array(PODValueSchema)),
isNotMemberOf: v.optional(v.array(PODValueSchema)),
isRevealed: v.optional(v.boolean()),
equalsEntry: v.optional(v.string())
}),
v.object({
type: v.literal("eddsa_pubkey"),
isMemberOf: v.optional(v.array(PODValueSchema)),
isNotMemberOf: v.optional(v.array(PODValueSchema)),
isRevealed: v.optional(v.boolean()),
equalsEntry: v.optional(v.string())
})
]);

const OptionalEntrySchema = v.object({
type: v.literal("optional"),
innerType: DefinedEntrySchema
});

const EntrySchema = v.variant("type", [
DefinedEntrySchema,
OptionalEntrySchema
]);

const PODTupleSchema = v.object({
entries: v.array(v.string()),
isMemberOf: v.optional(v.array(v.array(PODValueSchema))),
isNotMemberOf: v.optional(v.array(v.array(PODValueSchema)))
});

const SignerPublicKeySchema = v.object({
isMemberOf: v.optional(v.array(v.string())),
isNotMemberOf: v.optional(v.array(v.string()))
});

const SignatureSchema = v.object({
isMemberOf: v.optional(v.array(v.string())),
isNotMemberOf: v.optional(v.array(v.string()))
});

const PODSchemaSchema = v.object({
entries: v.record(v.string(), EntrySchema),
tuples: v.optional(v.array(PODTupleSchema)),
signerPublicKey: v.optional(SignerPublicKeySchema),
signature: v.optional(SignatureSchema),
meta: v.optional(
v.object({
labelEntry: v.string()
})
)
});

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(), ProofConfigPODSchemaSchema),
externalNullifier: v.optional(PODValueSchema),
watermark: v.optional(PODValueSchema)
});

const ProveResultSchema = v.variant("success", [
v.object({
success: v.literal(true),
// TODO: More specific schemas for these
proof: v.custom<Groth16Proof>(() => true),
boundConfig: v.custom<GPCBoundConfig>(() => true),
revealedClaims: v.custom<GPCRevealedClaims>(() => true)
}),
v.object({
success: v.literal(false),
error: v.string()
})
]);
import {
PODEntriesSchema,
PODSchemaSchema,
PodspecProofRequestSchema,
ProveResultSchema
} from "./schema_elements.js";

export type RPCFunction<
I extends v.TupleSchema<
Expand Down Expand Up @@ -173,6 +50,10 @@ export const ParcnetRPCSchema = {
output: v.boolean()
},
verify: {
input: v.tuple([v.any(), v.any(), v.any()]),
output: v.boolean()
},
verifyWithProofRequest: {
input: v.tuple([v.any(), v.any(), v.any(), v.any()]),
output: v.boolean()
}
Expand Down
Loading

0 comments on commit 76806d0

Please sign in to comment.