From 73b6fbde607bec22f510c571cfca01089830d471 Mon Sep 17 00:00:00 2001 From: Rob Knight Date: Tue, 22 Oct 2024 02:20:31 +0200 Subject: [PATCH] New RPC method for signing with prefixed keys (#19) --- apps/client-web/CHANGELOG.md | 8 ++ apps/client-web/package.json | 2 +- apps/client-web/src/App.tsx | 9 +- apps/client-web/src/client/client.ts | 6 +- apps/client-web/src/client/pod.ts | 29 ++++- apps/client-web/src/pages/HostedZapp.tsx | 14 ++- examples/test-app/CHANGELOG.md | 9 ++ examples/test-app/package.json | 2 +- examples/test-app/src/apis/PODSection.tsx | 100 +++++++++++++++++- packages/app-connector/CHANGELOG.md | 8 ++ packages/app-connector/package.json | 2 +- packages/app-connector/src/api_wrapper.ts | 4 + packages/app-connector/src/rpc_client.ts | 7 ++ packages/client-helpers/CHANGELOG.md | 7 ++ packages/client-helpers/package.json | 2 +- packages/client-rpc/CHANGELOG.md | 6 ++ packages/client-rpc/package.json | 2 +- packages/client-rpc/src/rpc_interfaces.ts | 1 + packages/client-rpc/src/schema.ts | 4 + packages/eslint-config/eslint.base.config.mjs | 3 +- packages/ticket-spec/CHANGELOG.md | 7 ++ packages/ticket-spec/package.json | 2 +- 22 files changed, 217 insertions(+), 17 deletions(-) diff --git a/apps/client-web/CHANGELOG.md b/apps/client-web/CHANGELOG.md index 7904e24..4870b4c 100644 --- a/apps/client-web/CHANGELOG.md +++ b/apps/client-web/CHANGELOG.md @@ -1,5 +1,13 @@ # client-web +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @parcnet-js/client-rpc@1.1.1 + - @parcnet-js/client-helpers@1.0.2 + ## 0.0.10 ### Patch Changes diff --git a/apps/client-web/package.json b/apps/client-web/package.json index 9ece4c2..c369b88 100644 --- a/apps/client-web/package.json +++ b/apps/client-web/package.json @@ -1,7 +1,7 @@ { "name": "client-web", "private": true, - "version": "0.0.10", + "version": "0.0.11", "type": "module", "scripts": { "dev": "vite", diff --git a/apps/client-web/src/App.tsx b/apps/client-web/src/App.tsx index 7ea029b..dfdb002 100644 --- a/apps/client-web/src/App.tsx +++ b/apps/client-web/src/App.tsx @@ -34,7 +34,8 @@ function App() { if ( state.advice && state.connectionState === ConnectionState.AUTHORIZED && - state.zapp + state.zapp && + state.zappOrigin ) { state.advice.hideClient(); state.advice.ready( @@ -43,7 +44,8 @@ function App() { state.pods, dispatch, state.identity, - state.zapp + state.zapp, + state.zappOrigin ) ); } @@ -53,7 +55,8 @@ function App() { state.pods, state.identity, dispatch, - state.zapp + state.zapp, + state.zappOrigin ]); return ( diff --git a/apps/client-web/src/client/client.ts b/apps/client-web/src/client/client.ts index 088c03b..c97a66a 100644 --- a/apps/client-web/src/client/client.ts +++ b/apps/client-web/src/client/client.ts @@ -27,7 +27,8 @@ export class ParcnetClientProcessor implements ParcnetRPC { private readonly pods: PODCollectionManager, dispatch: Dispatch, userIdentity: Identity, - zapp: Zapp + zapp: Zapp, + zappOrigin: string ) { this.subscriptions = new QuerySubscriptions(this.pods); this.subscriptions.onSubscriptionUpdated((update, serial) => { @@ -37,7 +38,8 @@ export class ParcnetClientProcessor implements ParcnetRPC { this.pods, this.subscriptions, userIdentity, - zapp + zapp, + zappOrigin ); this.identity = new ParcnetIdentityProcessor(userIdentity, zapp); this.gpc = new ParcnetGPCProcessor( diff --git a/apps/client-web/src/client/pod.ts b/apps/client-web/src/client/pod.ts index 0b8e78b..680c4e3 100644 --- a/apps/client-web/src/client/pod.ts +++ b/apps/client-web/src/client/pod.ts @@ -16,7 +16,8 @@ export class ParcnetPODProcessor implements ParcnetPODRPC { private readonly pods: PODCollectionManager, private readonly subscriptions: QuerySubscriptions, private readonly identity: Identity, - private readonly zapp: Zapp + private readonly zapp: Zapp, + private readonly zappOrigin: string ) {} public async query( @@ -94,4 +95,30 @@ export class ParcnetPODProcessor implements ParcnetPODRPC { ); return podToPODData(pod); } + + public async signPrefixed(entries: PODEntries): Promise { + const permission = this.zapp.permissions.SIGN_POD; + if (!permission) { + throw new MissingPermissionError("SIGN_POD", "pod.signPrefixed"); + } + + for (const name of Object.keys(entries)) { + if (!name.startsWith("_UNSAFE_")) { + throw new Error( + "PODs signed with signPrefixed must have a prefix of _UNSAFE_" + ); + } + } + + entries.UNSAFE_META_ORIGIN = { + type: "string", + value: this.zappOrigin + }; + + const pod = POD.sign( + entries, + encodePrivateKey(Buffer.from(this.identity.export(), "base64")) + ); + return podToPODData(pod); + } } diff --git a/apps/client-web/src/pages/HostedZapp.tsx b/apps/client-web/src/pages/HostedZapp.tsx index 9f73a17..ba085ef 100644 --- a/apps/client-web/src/pages/HostedZapp.tsx +++ b/apps/client-web/src/pages/HostedZapp.tsx @@ -28,7 +28,7 @@ export function HostedZapp(): ReactNode { }, [dispatch]); useEffect(() => { - if (state.advice && state.zapp) { + if (state.advice && state.zapp && state.zappOrigin) { state.advice.hideClient(); state.advice.ready( new ParcnetClientProcessor( @@ -36,11 +36,19 @@ export function HostedZapp(): ReactNode { state.pods, dispatch, state.identity, - state.zapp + state.zapp, + state.zappOrigin ) ); } - }, [state.advice, state.pods, state.identity, dispatch, state.zapp]); + }, [ + state.advice, + state.pods, + state.identity, + dispatch, + state.zapp, + state.zappOrigin + ]); const modalVisible = useMemo(() => { return state.proofInProgress !== undefined; diff --git a/examples/test-app/CHANGELOG.md b/examples/test-app/CHANGELOG.md index d7c50b4..ae59a32 100644 --- a/examples/test-app/CHANGELOG.md +++ b/examples/test-app/CHANGELOG.md @@ -1,5 +1,14 @@ # test-app +## 1.0.13 + +### Patch Changes + +- Updated dependencies + - @parcnet-js/app-connector@1.1.1 + - @parcnet-js/client-rpc@1.1.1 + - @parcnet-js/ticket-spec@1.1.1 + ## 1.0.12 ### Patch Changes diff --git a/examples/test-app/package.json b/examples/test-app/package.json index f3b829e..9e576c0 100644 --- a/examples/test-app/package.json +++ b/examples/test-app/package.json @@ -1,6 +1,6 @@ { "name": "test-app", - "version": "1.0.12", + "version": "1.0.13", "private": true, "type": "module", "scripts": { diff --git a/examples/test-app/src/apis/PODSection.tsx b/examples/test-app/src/apis/PODSection.tsx index 368bb3c..5917fc8 100644 --- a/examples/test-app/src/apis/PODSection.tsx +++ b/examples/test-app/src/apis/PODSection.tsx @@ -22,6 +22,8 @@ export function PODSection(): ReactNode {

Sign POD

+

Sign POD with Prefix

+

Insert POD

Delete POD

@@ -134,6 +136,7 @@ type Action = | { type: "ADD_ENTRY"; value: PODValue; + prefix?: string; } | { type: "REMOVE_ENTRY"; @@ -150,7 +153,7 @@ const editPODReducer = function ( switch (action.type) { case "ADD_ENTRY": { const newState = { ...state }; - let key = "entry"; + let key = `${action.prefix ?? ""}entry`; let n = 1; while (key in newState) { key = `entry${n}`; @@ -308,6 +311,101 @@ ${Object.entries(entries) ); } +function SignPODWithPrefix({ + z, + setSignedPOD +}: { + z: ParcnetAPI; + setSignedPOD: Dispatch>; +}): ReactNode { + const [creationState, setCreationState] = useState( + PODCreationState.None + ); + const [pod, setPOD] = useState(null); + const [entries, dispatch] = useReducer(editPODReducer, { + _UNSAFE_test: { type: "string", value: "Testing" } + } satisfies PODEntries); + return ( +
+

+ To sign a POD, first we have to create the entries. Note that the + entries must have a prefix of _UNSAFE_. Select the entries + for the POD below: +

+
+ {Object.entries(entries).map(([name, value], index) => ( + + ))} + +
+

+ Then we can sign the POD: + + {`const pod = await z.pod.sign({ +${Object.entries(entries) + .map(([key, value]) => { + return ` ${key}: { type: "${value.type}", value: ${ + bigintish.includes(value.type) + ? `${value.value.toString()}n` + : `"${value.value.toString()}"` + } }`; + }) + .join(",\n")} +}); + +`} + +

+ { + try { + const pod = await z.pod.signPrefixed(entries); + setPOD(pod); + setSignedPOD(pod); + setCreationState(PODCreationState.Success); + } catch (e) { + console.error(e); + setCreationState(PODCreationState.Failure); + } + }} + label="Sign POD with Prefix" + /> + {creationState !== PODCreationState.None && ( +
+ {creationState === PODCreationState.Success && ( +
+ POD signed successfully! The signature is{" "} + + {pod?.signature} + +
+ )} + {creationState === PODCreationState.Failure && ( +
An error occurred while signing your POD.
+ )} +
+ )} +
+ ); +} + function EditPODEntry({ name, value, diff --git a/packages/app-connector/CHANGELOG.md b/packages/app-connector/CHANGELOG.md index 224725b..51be6c0 100644 --- a/packages/app-connector/CHANGELOG.md +++ b/packages/app-connector/CHANGELOG.md @@ -1,5 +1,13 @@ # @parcnet-js/app-connector +## 1.1.1 + +### Patch Changes + +- Support 'signPrefixed' for unsafely-signed PODs +- Updated dependencies + - @parcnet-js/client-rpc@1.1.1 + ## 1.1.0 ### Minor Changes diff --git a/packages/app-connector/package.json b/packages/app-connector/package.json index 840217c..90e6af1 100644 --- a/packages/app-connector/package.json +++ b/packages/app-connector/package.json @@ -1,6 +1,6 @@ { "name": "@parcnet-js/app-connector", - "version": "1.1.0", + "version": "1.1.1", "license": "GPL-3.0-or-later", "type": "module", "main": "dist/index.cjs", diff --git a/packages/app-connector/src/api_wrapper.ts b/packages/app-connector/src/api_wrapper.ts index ac4a617..0c265ab 100644 --- a/packages/app-connector/src/api_wrapper.ts +++ b/packages/app-connector/src/api_wrapper.ts @@ -116,6 +116,10 @@ export class ParcnetPODWrapper { this.#api.pod.sign(entries).then(resolve).catch(reject); }); } + + async signPrefixed(entries: PODEntries): Promise { + return this.#api.pod.signPrefixed(entries); + } } /** diff --git a/packages/app-connector/src/rpc_client.ts b/packages/app-connector/src/rpc_client.ts index 942e151..7861cad 100644 --- a/packages/app-connector/src/rpc_client.ts +++ b/packages/app-connector/src/rpc_client.ts @@ -165,6 +165,13 @@ export class ParcnetRPCConnector implements ParcnetRPC, ParcnetEvents { [entries], ParcnetRPCSchema.pod.sign ); + }, + signPrefixed: async (entries: PODEntries): Promise => { + return this.#typedInvoke( + "pod.signPrefixed", + [entries], + ParcnetRPCSchema.pod.signPrefixed + ); } }; this.gpc = { diff --git a/packages/client-helpers/CHANGELOG.md b/packages/client-helpers/CHANGELOG.md index c77b355..feb1c77 100644 --- a/packages/client-helpers/CHANGELOG.md +++ b/packages/client-helpers/CHANGELOG.md @@ -1,5 +1,12 @@ # @parcnet-js/client-helpers +## 1.0.2 + +### Patch Changes + +- Updated dependencies + - @parcnet-js/client-rpc@1.1.1 + ## 1.0.1 ### Patch Changes diff --git a/packages/client-helpers/package.json b/packages/client-helpers/package.json index 9b90539..cebbd88 100644 --- a/packages/client-helpers/package.json +++ b/packages/client-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@parcnet-js/client-helpers", - "version": "1.0.1", + "version": "1.0.2", "license": "GPL-3.0-or-later", "type": "module", "main": "dist/index.js", diff --git a/packages/client-rpc/CHANGELOG.md b/packages/client-rpc/CHANGELOG.md index d6dd508..0731a7d 100644 --- a/packages/client-rpc/CHANGELOG.md +++ b/packages/client-rpc/CHANGELOG.md @@ -1,5 +1,11 @@ # @parcnet-js/client-rpc +## 1.1.1 + +### Patch Changes + +- Support 'signPrefixed' for unsafely-signed PODs + ## 1.1.0 ### Minor Changes diff --git a/packages/client-rpc/package.json b/packages/client-rpc/package.json index 6dbb84e..5cd841c 100644 --- a/packages/client-rpc/package.json +++ b/packages/client-rpc/package.json @@ -1,7 +1,7 @@ { "name": "@parcnet-js/client-rpc", "type": "module", - "version": "1.1.0", + "version": "1.1.1", "license": "GPL-3.0-or-later", "main": "dist/index.js", "module": "dist/index.js", diff --git a/packages/client-rpc/src/rpc_interfaces.ts b/packages/client-rpc/src/rpc_interfaces.ts index 959525f..24738e8 100644 --- a/packages/client-rpc/src/rpc_interfaces.ts +++ b/packages/client-rpc/src/rpc_interfaces.ts @@ -78,6 +78,7 @@ export interface ParcnetPODRPC { unsubscribe: (subscriptionId: string) => Promise; // Returns serialized POD sign: (entries: PODEntries) => Promise; + signPrefixed: (entries: PODEntries) => Promise; } export interface ParcnetRPC { diff --git a/packages/client-rpc/src/schema.ts b/packages/client-rpc/src/schema.ts index 8742aad..5752f3a 100644 --- a/packages/client-rpc/src/schema.ts +++ b/packages/client-rpc/src/schema.ts @@ -119,6 +119,10 @@ export const ParcnetRPCSchema = { sign: { input: v.tuple([PODEntriesSchema] as [entries: typeof PODEntriesSchema]), output: PODDataSchema + }, + signPrefixed: { + input: v.tuple([PODEntriesSchema] as [entries: typeof PODEntriesSchema]), + output: PODDataSchema } } } as const satisfies RPCSchema; diff --git a/packages/eslint-config/eslint.base.config.mjs b/packages/eslint-config/eslint.base.config.mjs index afb8142..202fff0 100644 --- a/packages/eslint-config/eslint.base.config.mjs +++ b/packages/eslint-config/eslint.base.config.mjs @@ -71,7 +71,8 @@ export default tseslint.config( "import/no-extraneous-dependencies": "error", "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/no-import-type-side-effects": "error", - "no-unexpected-multiline": "off" + "no-unexpected-multiline": "off", + "no-restricted-globals": ["error", "origin"] } } ); diff --git a/packages/ticket-spec/CHANGELOG.md b/packages/ticket-spec/CHANGELOG.md index 06696ce..543905d 100644 --- a/packages/ticket-spec/CHANGELOG.md +++ b/packages/ticket-spec/CHANGELOG.md @@ -1,5 +1,12 @@ # @parcnet-js/ticket-spec +## 1.1.1 + +### Patch Changes + +- Updated dependencies + - @parcnet-js/client-rpc@1.1.1 + ## 1.1.0 ### Minor Changes diff --git a/packages/ticket-spec/package.json b/packages/ticket-spec/package.json index 1f84292..7cc18ad 100644 --- a/packages/ticket-spec/package.json +++ b/packages/ticket-spec/package.json @@ -1,6 +1,6 @@ { "name": "@parcnet-js/ticket-spec", - "version": "1.1.0", + "version": "1.1.1", "license": "GPL-3.0-or-later", "type": "module", "main": "dist/index.js",