Skip to content

Commit

Permalink
Basic signing of PODs
Browse files Browse the repository at this point in the history
  • Loading branch information
robknight committed Sep 14, 2024
1 parent d893f1e commit 4f6bff9
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 42 deletions.
1 change: 1 addition & 0 deletions apps/client-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@pcd/gpc": "0.0.6",
"@pcd/pod": "0.1.5",
"@pcd/proto-pod-gpc-artifacts": "^0.5.0",
"@semaphore-protocol/identity": "^4.0.3",
"eventemitter3": "^5.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
11 changes: 8 additions & 3 deletions apps/client-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
} from "react";
import { ParcnetClientProcessor } from "./client/client";
import { PODCollection } from "./client/pod_collection";
import { loadPODsFromStorage, savePODsToStorage } from "./client/utils";
import {
getIdentity,
loadPODsFromStorage,
savePODsToStorage
} from "./client/utils";
import { Rabbit } from "./rabbit";
import { ClientAction, clientReducer, ClientState } from "./state";

Expand All @@ -23,7 +27,8 @@ function App() {
advice: null,
zapp: null,
authorized: false,
proofInProgress: undefined
proofInProgress: undefined,
identity: getIdentity()
});

useEffect(() => {
Expand All @@ -48,7 +53,7 @@ function App() {
savePODsToStorage(pods.getAll());
});
state.advice.ready(
new ParcnetClientProcessor(state.advice, pods, dispatch)
new ParcnetClientProcessor(state.advice, pods, dispatch, getIdentity())
);
}
}, [state.authorized, state.advice]);
Expand Down
10 changes: 8 additions & 2 deletions apps/client-web/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ParcnetPODRPC,
ParcnetRPC
} from "@parcnet/client-rpc";
import { Identity } from "@semaphore-protocol/identity";
import { Dispatch } from "react";
import { ClientAction } from "../state.js";
import { ParcnetGPCProcessor } from "./gpc.js";
Expand All @@ -23,13 +24,18 @@ export class ParcnetClientProcessor implements ParcnetRPC {
public constructor(
public readonly clientChannel: ConnectorAdvice,
private readonly pods: PODCollection,
dispatch: Dispatch<ClientAction>
dispatch: Dispatch<ClientAction>,
userIdentity: Identity
) {
this.subscriptions = new QuerySubscriptions(this.pods);
this.subscriptions.onSubscriptionUpdated((update, serial) => {
this.clientChannel.subscriptionUpdate(update, serial);
});
this.pod = new ParcnetPODProcessor(this.pods, this.subscriptions);
this.pod = new ParcnetPODProcessor(
this.pods,
this.subscriptions,
userIdentity
);
this.identity = new ParcnetIdentityProcessor();
this.gpc = new ParcnetGPCProcessor(this.pods, dispatch, this.clientChannel);
}
Expand Down
29 changes: 19 additions & 10 deletions apps/client-web/src/client/pod.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { ParcnetPODRPC } from "@parcnet/client-rpc";
import { EntriesSchema, PODSchema } from "@parcnet/podspec";
import { POD } from "@pcd/pod";
import { ParcnetPODRPC, PODQuery } from "@parcnet/client-rpc";
import { encodePrivateKey, POD, PODEntries } from "@pcd/pod";
import { Identity } from "@semaphore-protocol/identity";
import { PODCollection } from "./pod_collection.js";
import { QuerySubscriptions } from "./query_subscriptions.js";

export class ParcnetPODProcessor implements ParcnetPODRPC {
public constructor(
private readonly pods: PODCollection,
private readonly subscriptions: QuerySubscriptions
private readonly subscriptions: QuerySubscriptions,
private readonly identity: Identity
) {}

public async query<E extends EntriesSchema>(
query: PODSchema<E>
): Promise<string[]> {
public async query(query: PODQuery): Promise<string[]> {
return this.pods.query(query).map((pod) => pod.serialize());
}

Expand All @@ -25,13 +24,23 @@ export class ParcnetPODProcessor implements ParcnetPODRPC {
this.pods.delete(signature);
}

public async subscribe<E extends EntriesSchema>(
query: PODSchema<E>
): Promise<string> {
public async subscribe(query: PODQuery): Promise<string> {
return this.subscriptions.subscribe(query);
}

public async unsubscribe(subscriptionId: string): Promise<void> {
this.subscriptions.unsubscribe(subscriptionId);
}

public async sign(entries: PODEntries): Promise<string> {
/**
* @todo: Once we have decided how to restrict this, we would implement
* some security restrictions here.
*/
const pod = POD.sign(
entries,
encodePrivateKey(Buffer.from(this.identity.export(), "base64"))
);
return pod.serialize();
}
}
15 changes: 15 additions & 0 deletions apps/client-web/src/client/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { POD } from "@pcd/pod";
import { Identity } from "@semaphore-protocol/identity";

export function loadPODsFromStorage(): POD[] {
let pods: POD[] = [];
Expand All @@ -21,3 +22,17 @@ export function savePODsToStorage(pods: POD[]): void {
const serializedPODs = pods.map((pod) => pod.serialize());
localStorage.setItem("pod_collection", JSON.stringify(serializedPODs));
}

export function getIdentity(): Identity {
const serializedIdentity = localStorage.getItem("identity");

let identity: Identity;
if (!serializedIdentity) {
identity = new Identity();
localStorage.setItem("identity", identity.export());
} else {
identity = Identity.import(serializedIdentity);
}

return identity;
}
2 changes: 2 additions & 0 deletions apps/client-web/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConnectorAdvice } from "@parcnet/client-helpers";
import { ProveResult, Zapp } from "@parcnet/client-rpc";
import { PodspecProofRequest } from "@parcnet/podspec";
import { POD } from "@pcd/pod";
import { Identity } from "@semaphore-protocol/identity";

export type ClientState = {
loggedIn: boolean;
Expand All @@ -17,6 +18,7 @@ export type ClientState = {
resolve?: (result: ProveResult) => void;
}
| undefined;
identity: Identity;
};

export type ClientAction =
Expand Down
104 changes: 79 additions & 25 deletions examples/test-app/src/apis/PODSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@ import { ParcnetAPI, Subscription } from "@parcnet/app-connector";
import * as p from "@parcnet/podspec";
import { POD, POD_INT_MAX, POD_INT_MIN, PODEntries, PODValue } from "@pcd/pod";
import JSONBig from "json-bigint";
import { ReactNode, useReducer, useState } from "react";
import {
Dispatch,
ReactNode,
SetStateAction,
useReducer,
useState
} from "react";
import { Button } from "../components/Button";
import { TryIt } from "../components/TryIt";
import { useParcnetClient } from "../hooks/useParcnetClient";

const MAGIC_PRIVATE_KEY =
"00112233445566778899AABBCCDDEEFF00112233445566778899aabbccddeeff";

export function PODSection(): ReactNode {
const { z, connected } = useParcnetClient();
const [pod, setPOD] = useState<POD | null>(null);

return !connected ? null : (
<div>
<h1 className="text-xl font-bold mb-2">PODs</h1>
<div className="prose">
<h2 className="text-lg font-bold mt-4">Query PODs</h2>
<QueryPODs z={z} />
<h2 className="text-lg font-bold mt-4">Sign POD</h2>
<SignPOD z={z} setSignedPOD={setPOD} />
<h2 className="text-lg font-bold mt-4">Insert POD</h2>
<InsertPOD z={z} />
<InsertPOD z={z} pod={pod} />
<h2 className="text-lg font-bold mt-4">Delete POD</h2>
<DeletePOD z={z} />
<h2 className="text-lg font-bold mt-4">Subscribe to PODs</h2>
Expand Down Expand Up @@ -122,7 +128,7 @@ type Action =
const stringish = ["string", "eddsa_pubkey"];
const bigintish = ["int", "cryptographic"];

const insertPODReducer = function (
const editPODReducer = function (
state: PODEntries,
action: Action
): PODEntries {
Expand Down Expand Up @@ -186,23 +192,29 @@ enum PODCreationState {
Failure
}

function InsertPOD({ z }: { z: ParcnetAPI }): ReactNode {
function SignPOD({
z,
setSignedPOD
}: {
z: ParcnetAPI;
setSignedPOD: Dispatch<SetStateAction<POD | null>>;
}): ReactNode {
const [creationState, setCreationState] = useState<PODCreationState>(
PODCreationState.None
);
const [signature, setSignature] = useState<string>("");
const [entries, dispatch] = useReducer(insertPODReducer, {
const [pod, setPOD] = useState<POD | null>(null);
const [entries, dispatch] = useReducer(editPODReducer, {
test: { type: "string", value: "Testing" }
} satisfies PODEntries);
return (
<div>
<p>
To insert a POD, first we have to create one. Select the entries for the
POD below:
To sign a POD, first we have to create the entries. Select the entries
for the POD below:
</p>
<div className="flex flex-col gap-2 mb-4">
{Object.entries(entries).map(([name, value], index) => (
<InsertPODEntry
<EditPODEntry
key={index}
showLabels={index === 0}
name={name}
Expand All @@ -223,9 +235,9 @@ function InsertPOD({ z }: { z: ParcnetAPI }): ReactNode {
</Button>
</div>
<p>
Then we can insert the POD:
Then we can sign the POD:
<code className="block text-xs font-base rounded-md p-2 whitespace-pre">
{`const pod = POD.sign({
{`const pod = await z.pod.sign({
${Object.entries(entries)
.map(([key, value]) => {
return ` ${key}: { type: "${value.type}", value: ${
Expand All @@ -235,44 +247,45 @@ ${Object.entries(entries)
} }`;
})
.join(",\n")}
}, privateKey);
});
await z.pod.insert(pod);`}
`}
</code>
</p>
<TryIt
onClick={async () => {
try {
const pod = POD.sign(entries, MAGIC_PRIVATE_KEY);
await z.pod.insert(pod);
setSignature(pod.signature);
const pod = await z.pod.sign(entries);
setPOD(pod);
setSignedPOD(pod);
setCreationState(PODCreationState.Success);
} catch (_e) {
} catch (e) {
console.error(e);
setCreationState(PODCreationState.Failure);
}
}}
label="Insert POD"
label="Sign POD"
/>
{creationState !== PODCreationState.None && (
<div className="my-2">
{creationState === PODCreationState.Success && (
<div>
POD inserted successfully! The signature is{" "}
POD signed successfully! The signature is{" "}
<code className="block text-xs font-base rounded-md p-2 whitespace-pre">
{signature}
{pod?.signature}
</code>
</div>
)}
{creationState === PODCreationState.Failure && (
<div>An error occurred while inserting your POD.</div>
<div>An error occurred while signing your POD.</div>
)}
</div>
)}
</div>
);
}

function InsertPODEntry({
function EditPODEntry({
name,
value,
type,
Expand Down Expand Up @@ -341,6 +354,47 @@ function InsertPODEntry({
);
}

function InsertPOD({ z, pod }: { z: ParcnetAPI; pod: POD }): ReactNode {
const [insertionState, setInsertionState] = useState<PODCreationState>(
PODCreationState.None
);
return (
<div>
<p>
To insert a POD, we must first create it. You can create a new POD by
using the "Sign POD" section above.
</p>
<p>
<code className="block text-xs font-base rounded-md p-2 whitespace-pre-wrap">
{`await z.pod.insert(pod);`}
</code>
</p>

<TryIt
onClick={async () => {
try {
await z.pod.insert(pod);
setInsertionState(PODCreationState.Success);
} catch (_e) {
setInsertionState(PODCreationState.Failure);
}
}}
label="Insert POD"
/>
{insertionState !== PODCreationState.None && (
<div className="my-2">
{insertionState === PODCreationState.Success && (
<div>POD inserted successfully!</div>
)}
{insertionState === PODCreationState.Failure && (
<div>An error occurred while inserting your POD.</div>
)}
</div>
)}
</div>
);
}

enum PODDeletionState {
None,
Success,
Expand Down
7 changes: 6 additions & 1 deletion packages/app-connector/src/api_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "@parcnet/client-rpc";
import * as p from "@parcnet/podspec";
import { GPCBoundConfig, GPCProof, GPCRevealedClaims } from "@pcd/gpc";
import { POD } from "@pcd/pod";
import { POD, PODEntries } from "@pcd/pod";
import { EventEmitter } from "eventemitter3";
import { PodspecProofRequest } from "../../podspec/src/index.js";
import { ParcnetRPCConnector } from "./rpc_client.js";
Expand Down Expand Up @@ -81,6 +81,11 @@ class ParcnetPODWrapper {
async delete(signature: string): Promise<void> {
return this.#api.pod.delete(signature);
}

async sign(entries: PODEntries): Promise<POD> {
const pod = await this.#api.pod.sign(entries);
return POD.deserialize(pod);
}
}

class ParcnetGPCWrapper {
Expand Down
Loading

0 comments on commit 4f6bff9

Please sign in to comment.