Skip to content

Commit

Permalink
add journal parser (#8)
Browse files Browse the repository at this point in the history
* add journal parser

* cleanup
  • Loading branch information
cameronfyfe authored Mar 8, 2024
1 parent 803d4dc commit 6dfdc69
Show file tree
Hide file tree
Showing 8 changed files with 2,324 additions and 21 deletions.
4 changes: 3 additions & 1 deletion react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"test": "jest"
},
"dependencies": {
"@eqtylab/risc-zero-verifier": "*"
"@eqtylab/risc-zero-verifier": "*",
"react-json-tree": "^0.18.0",
"react-markdown": "^9.0.1"
},
"peerDependencies": {
"react": "^18.2.0",
Expand Down
191 changes: 191 additions & 0 deletions react/src/JournalParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React, { useEffect, useMemo, useState } from 'react';
import { JSONTree } from "react-json-tree";
import Markdown from "react-markdown";

const DEFAULT_REGISTRY = 'https://raw.githubusercontent.com/cameronfyfe/risc0-journal-parser-registry/main/registry.json';

const cssPrefix = "risc-zero-journal-parser";

function cssClass(className) {
return `${cssPrefix}-${className}`;
}

function JournalParser({
guestCodeId,
journalBytes,
registryUrl = DEFAULT_REGISTRY,
ipfsGateway = "https://ipfs.io",
}) {
const journalBytesString = useMemo(() =>
journalBytes ? JSON.stringify(Array.from(journalBytes)) : null,
[journalBytes]
);

const [registry, setRegistry] = useState(null);
useEffect(() => {
(async () => {
try {
const response = await fetch(registryUrl);
const json = await response.json();
setRegistry(json);
} catch {
setRegistry(null);
}
})();
}, [registryUrl]);

const parsers = useMemo(() => {
if (registry && guestCodeId) {
return registry.parsers.filter((parser) =>
parser.guestCodeId === guestCodeId
);
} else {
return [];
}
}, [registry, guestCodeId]);

const [selectedParserIndex, setSelectedParserIndex] = useState(undefined);

useEffect(() => {
if (parsers && parsers.length > 0) {
setSelectedParserIndex(0);
} else {
setSelectedParserIndex(undefined);
}
}, [parsers]);

const selectedParser = useMemo(
() => selectedParserIndex != undefined ? parsers[selectedParserIndex] : null,
[parsers, selectedParserIndex],
);

const wasmEntryUrl = useMemo(
() =>
selectedParser
? selectedParser.journalParser.protocol === "ipfs"
? `${ipfsGateway}/ipfs/${selectedParser.journalParser.src}`
: null
: null,
[selectedParser],
);

const [wasmModule, setWasmModule] = useState(null);
useEffect(() => {
if (wasmEntryUrl) {
(async () => {
// TODO: This is a hack to get around the fact that import() doesn't work with dynamic URLs
// when webpack/babel are configured to statically resolve imports.
// It's easy to disable globally in .babelrc, but then the
// 'import("@eqtylab/risc-zero-verifier")' in Verifier.js breaks.
// This hack works for now but is not ideal. We need to be careful what the value of
// wasmEntryUrl is to use this method with eval().
const wm = await eval(`import("${wasmEntryUrl}")`);
await wm.default();
setWasmModule(wm);
})();
} else {
setWasmModule(null);
}
}, [wasmEntryUrl]);

const getJsonObjectFromReceiptBinary = useMemo(() =>
wasmModule && wasmModule.json_obj_from_journal_bytes || null,
[wasmModule]
);

const getStatementFromReceiptBinary = useMemo(() =>
wasmModule && wasmModule.statement_from_journal_bytes || null,
[wasmModule]
);

const [journalObj, setJournalObj] = useState(null);
useEffect(() => {
(async () => {
if (journalBytes && getJsonObjectFromReceiptBinary) {
try {
const obj = await getJsonObjectFromReceiptBinary(journalBytes);
setJournalObj(obj);
} catch {
setJournalObj(null);
}
} else {
setJournalObj(null);
}
})();
}, [journalBytes, getJsonObjectFromReceiptBinary]);

const [statement, setStatement] = useState(null);
useEffect(() => {
(async () => {
if (journalBytes && getStatementFromReceiptBinary) {
try {
const stmt = await getStatementFromReceiptBinary(journalBytes);
setStatement(stmt);
} catch {
setStatement(null);
}
} else {
setStatement(null);
}
})();
}, [journalBytes, getStatementFromReceiptBinary]);

return (
<div>
Journal Parser:
<br />
{guestCodeId ? (
<select
value={selectedParserIndex}
onChange={(e) => setSelectedParserIndex(e.target.value)}
>
{parsers.map((parser, i) => (
<option key={i} value={i}>
{parser.profile.name} {parser.profile.version}
</option>
))}
</select>
) : (
<p><i>No parsers available for this guest code id.</i></p>
)}

<br /><br />

{statement &&
<div>
Statement:
<br />
<Markdown className={cssClass("statement-markdown")}>{statement}</Markdown>
</div>
}

<br /><br />

{journalObj &&
<div>
Journal (JSON):
<br />
<div>
<JSONTree data={journalObj} />
</div>
</div>
}

<br /><br />

{journalBytesString &&
<div>
Journal (RAW):
<br />
<textarea className={cssClass("journal-raw")}
value={journalBytesString}
readOnly={true}
/>
</div>
}

</div>
);
}

export default JournalParser;
35 changes: 32 additions & 3 deletions react/src/Verifier.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import JournalParser from './JournalParser.js';

const cssPrefix = "risc-zero-verifier";

const defaultText = {
export const defaultText = {
verifyButtonLabel: "Verify",
instructions: "Upload a receipt file as JSON or binary (bincode format), or paste it as JSON into the form field.",
fieldLabels: {
Expand All @@ -20,7 +21,12 @@ const defaultText = {
}
}

function Verifier({text = defaultText, instanceNumber = 0}) {
function Verifier({
text = defaultText,
instanceNumber = 0,
enableJournalParser = false,
ipfsGateway = "https://ipfs.io",
}) {
const [verifier, setVerifier] = useState(null);

useEffect(() => {
Expand All @@ -36,6 +42,16 @@ function Verifier({text = defaultText, instanceNumber = 0}) {
const [receiptJson, setReceiptJson] = useState('');
const [verificationResult, setVerificationResult] = useState('');

const receiptJournalBytes = useMemo(() => {
try {
const journal = JSON.parse(receiptJson).journal;
const journalBytes = new Uint8Array(journal.bytes);
return journalBytes;
} catch {
return null;
}
}, [receiptJson]);

async function verifyRiscZeroReceipt(guestCodeId, receiptJson) {
if (!guestCodeId) {
return text.errors.guestCodeIdMissing;
Expand Down Expand Up @@ -120,6 +136,19 @@ function Verifier({text = defaultText, instanceNumber = 0}) {
<button id={cssId("verify-button")} onClick={async () => setVerificationResult(await verifyRiscZeroReceipt(guestCodeId, receiptJson))}>{text.verifyButtonLabel}</button>
</div>
<div className={cssClass("receipt-verification-result")}>{verificationResult}</div>

<br/><br/>
<hr/>
<br/><br/>

{enableJournalParser && (
<JournalParser
guestCodeId={guestCodeId}
journalBytes={receiptJournalBytes}
ipfsGateway={ipfsGateway}
/>
)}

</div>
);
}
Expand Down
Loading

0 comments on commit 6dfdc69

Please sign in to comment.