Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GPC proofs in test client #3

Merged
merged 2 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/client-web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
public/artifacts/*
2 changes: 1 addition & 1 deletion apps/client-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PARCNET Client</title>
</head>
<body class="bg-black h-screen w-screen overflow-hidden">
<body class="bg-black h-screen w-screen overflow-scroll">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
5 changes: 2 additions & 3 deletions apps/client-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
"eventemitter3": "^5.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vite-plugin-node-polyfills": "^0.22.0",
"zod": "^3.23.8"
"vite-plugin-node-polyfills": "^0.22.0"
},
"devDependencies": {
"@parcnet/eslint-config": "workspace:*",
Expand All @@ -33,6 +32,6 @@
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"typescript": "^5.5",
"vite": "^5.4.1"
"vite": "^5.4.4"
}
}
278 changes: 256 additions & 22 deletions apps/client-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { listen } from "@parcnet/client-helpers/connection/iframe";
import { Zapp } from "@parcnet/client-rpc";
import { Dispatch, ReactNode, useEffect, useReducer } from "react";
import { EntriesSchema, PODSchema, proofRequest } from "@parcnet/podspec";
import { gpcProve } from "@pcd/gpc";
import { POD, POD_INT_MAX, POD_INT_MIN } from "@pcd/pod";
import {
Dispatch,
Fragment,
ReactNode,
useEffect,
useReducer,
useState
} from "react";
import { ParcnetClientProcessor } from "./client/client";
import { PODCollection } from "./client/pod_collection";
import { loadPODsFromStorage, savePODsToStorage } from "./client/utils";
Expand Down Expand Up @@ -67,44 +77,268 @@ function App() {
state.authorized &&
state.zapp &&
state.proofInProgress && (
<Prove proveOperation={state.proofInProgress} />
<Prove proveOperation={state.proofInProgress} dispatch={dispatch} />
)}
</div>
</main>
);
}

function Reveal({ children }: { children: ReactNode }) {
const [isRevealed, setIsRevealed] = useState(false);
return (
<>
<button onClick={() => setIsRevealed(!isRevealed)}>
{isRevealed ? "Hide" : "Reveal"}
</button>
{isRevealed && <div className="my-1">{children}</div>}
</>
);
}

function ProvePODInfo({
name,
schema,
pods,
selectedPOD,
onChange
}: {
name: string;
schema: PODSchema<EntriesSchema>;
pods: POD[];
selectedPOD: POD | undefined;
onChange: (pod: POD | undefined) => void;
}): ReactNode {
const revealedEntries = Object.entries(schema.entries)
.map(([name, entry]) => {
if (entry.type === "optional") {
entry = entry.innerType;
}
return [name, entry] as const;
})
.filter(([_, entry]) => entry.isRevealed);

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

const entriesWithConstraints = Object.entries(schema.entries)
.map(([name, entry]) => {
if (entry.type === "optional") {
entry = entry.innerType;
}
return [name, entry] as const;
})
.filter(
([_, entry]) =>
!!entry.isMemberOf ||
!!entry.isNotMemberOf ||
!!(entry.type === "int" && entry.inRange)
);

return (
<div className="py-2">
<div className="flex items-center gap-2">
<div className="font-semibold">{name}</div>
<select
className="block w-full rounded-md bg-gray-800 border-transparent focus:border-gray-500 focus:ring-0 p-2 text-sm"
value={selectedPOD?.signature ?? ""}
onChange={(ev) => {
onChange(pods.find((pod) => pod.signature === ev.target.value));
}}
>
<option value="" disabled>
-- None selected --
</option>
{pods.map((pod) => {
return (
<option key={pod.signature} value={pod.signature}>
{pod.signature.substring(0, 16)}
</option>
);
})}
</select>
</div>
<div className="mt-2 font-semibold">
{revealedEntries.length > 0 && "Revealed entries:"}
</div>
<div className="grid grid-cols-2 gap-2">
{revealedEntries.map(([entryName, _]) => {
return (
<Fragment key={`${name}-${entryName}`}>
<div>{entryName}</div>
<div>
{selectedPODEntries?.[entryName].value.toString() ?? "-"}
</div>
</Fragment>
);
})}
</div>
{entriesWithConstraints.length > 0 && (
<div className="mt-2">
<div className="font-semibold">Proven constraints:</div>
{entriesWithConstraints.map(([entryName, entry]) => {
return (
<div key={`${name}-${entryName}-constraints`} className="my-1">
{entry.isMemberOf && (
<div>
<span className="font-semibold">{entryName}</span> is member
of list:{" "}
<Reveal>
<div className="p-1 rounded bg-gray-800">
{entry.isMemberOf
.map((v) => v.value.toString())
.join(", ")}
</div>
</Reveal>
</div>
)}
{entry.isNotMemberOf && (
<div>
<span className="font-semibold">{entryName}</span> is not
member of list:{" "}
<Reveal>
<div className="p-1 rounded bg-gray-800">
{entry.isNotMemberOf
.map((v) => v.value.toString())
.join(", ")}
</div>
</Reveal>
</div>
)}
{entry.type === "int" && entry.inRange && (
<div className="flex gap-1 items-center">
<span className="font-semibold">{entryName}</span> is
<div className="p-1 rounded bg-gray-800">
{entry.inRange.min === POD_INT_MIN &&
entry.inRange.max === POD_INT_MAX &&
"any number"}
{entry.inRange.min !== POD_INT_MIN &&
entry.inRange.max === POD_INT_MAX &&
`greater than ${entry.inRange.min}`}
{entry.inRange.min === POD_INT_MIN &&
entry.inRange.max !== POD_INT_MAX &&
`less than ${entry.inRange.max}`}
{entry.inRange.min !== POD_INT_MIN &&
entry.inRange.max !== POD_INT_MAX &&
`between ${entry.inRange.min} and ${entry.inRange.max}`}
</div>
</div>
)}
</div>
);
})}
</div>
)}
</div>
);
}

function Prove({
proveOperation
proveOperation,
dispatch
}: {
proveOperation: NonNullable<ClientState["proofInProgress"]>;
dispatch: Dispatch<ClientAction>;
}): ReactNode {
const canProve =
Object.keys(proveOperation.selectedPods).length ===
Object.keys(proveOperation.proofRequest.pods).length &&
Object.values(proveOperation.selectedPods).every((maybePod) => !!maybePod);

/**
* show exactly which fields are revealed and which are not, literally show
* the whole POD and which entries are revealed
* could be complicated for tuples?
*/
return (
<div>
<ul>
{Object.entries(proveOperation.pods).map(([key, pods]) => {
return (
<div key={key}>
<p>{key}</p>
<div className="text-sm">
{pods.map((pod) => {
return (
<div className="text-sm" key={pod.signature}>
{pod.contentID.toString()}
{pod.serialize()}
</div>
);
})}
</div>
</div>
);
})}
</ul>
<div className="text-sm">
<p>This proof will reveal the following data from your PODs:</p>
{Object.entries(proveOperation.proofRequest.pods).map(
([name, schema]) => {
return (
<ProvePODInfo
key={name}
name={name}
schema={schema}
pods={proveOperation.pods[name]}
selectedPOD={proveOperation.selectedPods[name]}
onChange={(pod) => {
dispatch({
type: "set-proof-in-progress",
...proveOperation,
selectedPods: {
...proveOperation.selectedPods,
...{ [name]: pod }
}
});
}}
/>
);
}
)}
<div className="mt-4">
<button
disabled={!canProve}
className="border-2 text-center font-semibold cursor-pointer border-white py-1 px-2 uppercase active:translate-x-[2px] active:translate-y-[2px] disabled:border-gray-500 disabled:text-gray-500"
onClick={() => {
dispatch({
type: "set-proof-in-progress",
...proveOperation,
proving: true
});
const prs = proofRequest(
proveOperation.proofRequest
).getProofRequest();
gpcProve(
prs.proofConfig,
{
pods: proveOperation.selectedPods as Record<string, POD>,
membershipLists: prs.membershipLists,
watermark: prs.watermark
},
new URL("/artifacts", window.location.origin).toString()
)
.then((proof) => {
proveOperation.resolve?.({
success: true,
proof: proof.proof,
boundConfig: proof.boundConfig,
revealedClaims: proof.revealedClaims
});
dispatch({
type: "clear-proof-in-progress"
});
})
.catch((error) => console.error(error));
}}
>
{proveOperation.proving ? (
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
) : (
"Prove"
)}
</button>
</div>
</div>
</div>
);
}
Expand Down
9 changes: 1 addition & 8 deletions apps/client-web/src/client/gpc.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { ConnectorAdvice } from "@parcnet/client-helpers";
import {
ParcnetGPCRPC,
ParcnetRPCSchema,
ProveResult
} from "@parcnet/client-rpc";
import { ParcnetGPCRPC, ProveResult } from "@parcnet/client-rpc";
import { PodspecProofRequest, proofRequest } from "@parcnet/podspec";
import { Dispatch } from "react";
import { ClientAction } from "../state";
import { PODCollection } from "./pod_collection";
import { validateInput } from "./utils";

export class ParcnetGPCProcessor implements ParcnetGPCRPC {
public constructor(
Expand All @@ -17,7 +12,6 @@ export class ParcnetGPCProcessor implements ParcnetGPCRPC {
private readonly advice: ConnectorAdvice
) {}

@validateInput(ParcnetRPCSchema.shape.gpc.shape.canProve)
public async canProve(request: PodspecProofRequest): Promise<boolean> {
const prs = proofRequest(request);

Expand All @@ -31,7 +25,6 @@ export class ParcnetGPCProcessor implements ParcnetGPCRPC {
return true;
}

@validateInput(ParcnetRPCSchema.shape.gpc.shape.prove)
public async prove(request: PodspecProofRequest): Promise<ProveResult> {
const prs = proofRequest(request);

Expand Down
Loading
Loading