fix: ui updates and notary key parsing

This commit is contained in:
tsukino
2024-10-10 19:50:23 +08:00
parent b79696c9b8
commit fd1b1e025a
10 changed files with 157 additions and 284 deletions

View File

@@ -9,7 +9,6 @@ COPY . .
RUN curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y
RUN npm i
RUN npm i --prefix rs/verifier/
RUN npm i --prefix rs/0.1.0-alpha.6/
RUN npm i --prefix rs/0.1.0-alpha.7/
RUN npm run build

228
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ import { verify } from '../rs/verifier/index.node';
// @ts-ignore
import { verify as verifyV7 } from '../rs/0.1.0-alpha.7/index.node';
import { Attestation } from '../web/utils/types/types';
import { convertNotaryWsToHttp } from '../web/utils';
const app = express();
const port = 3000;
@@ -97,19 +98,37 @@ app.get('/ipfs/:cid', async (req, res) => {
* redirect to root if verification fails
*/
if (!jsonProof.version && jsonProof.notaryUrl) {
const proof = await verify(
file,
await fetchPublicKeyFromNotary(jsonProof.notaryUrl),
);
const notaryPem = await fetchPublicKeyFromNotary(jsonProof.notaryUrl);
const proof = await verify(file, notaryPem);
proof.notaryUrl = jsonProof.notaryUrl;
storeConfig.proofs.ipfs[req.params.cid].proof = proof;
storeConfig.proofs.ipfs[req.params.cid].proof = {
...proof,
version: '0.1.0-alpha.5',
notaryUrl: jsonProof.notaryUrl,
notaryKey: notaryPem,
};
} else if (jsonProof.version === '0.1.0-alpha.7') {
const proof = await verifyV7(
jsonProof.data,
await fetchPublicKeyFromNotary(jsonProof.meta.notaryUrl),
);
const notaryUrl = convertNotaryWsToHttp(jsonProof.meta.notaryUrl);
const notaryPem = await fetchPublicKeyFromNotary(notaryUrl);
const proof = await verifyV7(jsonProof.data, notaryPem);
proof.notaryUrl = jsonProof.meta.notaryUrl;
storeConfig.proofs.ipfs[req.params.cid].proof = proof;
storeConfig.proofs.ipfs[req.params.cid].proof = {
version: '0.1.0-alpha.7',
time: proof.time,
sent: proof.sent,
recv: proof.recv,
notaryUrl: notaryUrl,
websocketProxyUrl: jsonProof.meta.websocketProxyUrl,
notaryKey: Buffer.from(
notaryPem
.replace('-----BEGIN PUBLIC KEY-----', '')
.replace('-----END PUBLIC KEY-----', '')
.replace(/\n/g, ''),
'base64',
)
.slice(23)
.toString('hex'),
};
}
const store = configureAppStore(storeConfig);
@@ -205,14 +224,7 @@ app.listen(port, () => {
});
async function fetchPublicKeyFromNotary(notaryUrl: string) {
let host;
let httpUrl = notaryUrl.replace(/^ws:/, 'http:').replace(/^wss:/, 'https:');
let hostMatch = httpUrl.match(/^(https?:\/\/[^\/]+)/);
if (hostMatch) {
host = hostMatch[1];
}
const res = await fetch(host + '/info');
const res = await fetch(notaryUrl + '/info');
const json: any = await res.json();
if (!json.publicKey) throw new Error('invalid response');
return json.publicKey;

View File

@@ -7,7 +7,10 @@ import React, {
} from 'react';
import c from 'classnames';
import classNames from 'classnames';
import { Attestation, Proof as VerifiedProof } from '../../utils/types/types';
import {
Attestation,
AttestedData as VerifiedProof,
} from '../../utils/types/types';
import Modal, { ModalContent, ModalFooter, ModalHeader } from '../Modal';
import Icon from '../Icon';
import { useDispatch } from 'react-redux';
@@ -55,15 +58,15 @@ export default function ProofViewer(props: {
)}
<div className="flex flex-col px-2">
<div className="flex flex-row gap-2 items-center">
<TabLabel onClick={() => setTab('info')} active={tab === 'info'}>
Info
</TabLabel>
<TabLabel onClick={() => setTab('sent')} active={tab === 'sent'}>
Sent
</TabLabel>
<TabLabel onClick={() => setTab('recv')} active={tab === 'recv'}>
Recv
</TabLabel>
<TabLabel onClick={() => setTab('info')} active={tab === 'info'}>
Metadata
</TabLabel>
<div className="flex flex-row flex-grow items-center justify-end">
<button className="button" onClick={onClickShare}>
Share
@@ -73,15 +76,30 @@ export default function ProofViewer(props: {
</div>
<div className="flex flex-col flex-grow px-2">
{tab === 'info' && (
<div className="w-full bg-slate-100 text-slate-800 border p-2 text-xs break-all h-full outline-none font-mono">
<div>
<div>Notary URL:</div>
<div>
{props.proof.version === '0.1.0-alpha.7'
? props.proof.meta.notaryUrl
: <></>}
</div>
</div>
<div className="flex flex-col w-full bg-slate-100 text-slate-800 border p-2 text-xs break-all h-full outline-none font-mono gap-4">
<MetadataRow label="Version" value={props.verifiedProof.version} />
<MetadataRow
label="Notary URL"
value={props.verifiedProof.notaryUrl}
/>
{props.verifiedProof.websocketProxyUrl && (
<MetadataRow
label="Websocket Proxy URL"
value={props.verifiedProof.websocketProxyUrl}
/>
)}
{props.verifiedProof.verifierKey && (
<MetadataRow
label="Verifying Key"
value={props.verifiedProof.verifierKey}
/>
)}
{props.verifiedProof.notaryKey && (
<MetadataRow
label="Notary Key"
value={props.verifiedProof.notaryKey}
/>
)}
</div>
)}
{tab === 'sent' && (
@@ -103,6 +121,15 @@ export default function ProofViewer(props: {
);
}
function MetadataRow({ label, value }: { label: string; value: string }) {
return (
<div>
<div>{label}:</div>
<div className="text-sm font-semibold whitespace-pre-wrap">{value}</div>
</div>
);
}
function TabLabel(props: {
children: ReactNode;
onClick: MouseEventHandler;

View File

@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
import ProofViewer from '../ProofViewer';
import { FileDropdown } from '../FileDropdown';
import { PubkeyInput } from '../../pages/PubkeyInput';
import { Proof } from '../../utils/types/types';
import { AttestedData } from '../../utils/types/types';
import { File } from '@web-std/file';
import { verify } from '../../utils';
@@ -18,7 +18,7 @@ export default function SharedProof(): ReactElement {
const file = new File([JSON.stringify(proofData?.raw)], `${cid}.json`, {
type: 'text/plain',
});
const [verifiedProof, setVerifiedProof] = useState<Proof | null>(
const [verifiedProof, setVerifiedProof] = useState<AttestedData | null>(
proofData?.proof || null,
);

View File

@@ -3,7 +3,10 @@ import { useDispatch } from 'react-redux';
import { readFileAsync, safeParseJSON, verify } from '../../utils';
import FileUploadInput from '../../components/FileUploadInput';
import ProofViewer from '../../components/ProofViewer';
import { Attestation, Proof as VerifiedProof } from '../../utils/types/types';
import {
Attestation,
AttestedData as VerifiedProof,
} from '../../utils/types/types';
import { FileDropdown } from '../../components/FileDropdown';
import { PubkeyInput } from '../PubkeyInput';
@@ -25,6 +28,7 @@ export default function FileDrop(): ReactElement {
setVerifiedProof(resp);
setStep('result');
} catch (e: any) {
console.error(e);
if (e?.message !== 'Failed to fetch') {
setError(
typeof e === 'string'

View File

@@ -4,7 +4,7 @@ import type { Proof } from 'tlsn-js-v5/build/types';
import { useSelector } from 'react-redux';
import deepEqual from 'fast-deep-equal';
import { EXPLORER_URL } from '../utils/constants';
import { Attestation } from '../utils/types/types';
import { Attestation, AttestedData } from '../utils/types/types';
import { verify } from '../utils';
enum ActionType {
@@ -18,19 +18,14 @@ export type Action<payload = any> = {
meta?: any;
};
type ProofData = {
type AttestationData = {
raw: Attestation;
proof?: {
time: number;
sent: string;
recv: string;
notaryUrl: string;
};
proof?: AttestedData;
};
type State = {
ipfs: {
[cid: string]: ProofData;
[cid: string]: AttestationData;
};
};
@@ -66,7 +61,7 @@ export const fetchProofFromIPFS =
};
export const setIPFSProof = (
payload: ProofData & {
payload: AttestationData & {
cid: string;
},
) => ({
@@ -76,16 +71,11 @@ export const setIPFSProof = (
export default function proofs(
state = initState,
action: Action<{
cid: string;
raw: Proof;
proof: {
time: number;
sent: string;
recv: string;
notaryUrl: string;
};
}>,
action: Action<
AttestationData & {
cid: string;
}
>,
): State {
switch (action.type) {
case ActionType.SetIPFSProof:
@@ -104,7 +94,7 @@ export default function proofs(
}
}
export const useIPFSProof = (cid?: string): ProofData | null => {
export const useIPFSProof = (cid?: string): AttestationData | null => {
return useSelector((state: AppRootState) => {
if (!cid) return null;
return state.proofs.ipfs[cid] || null;

View File

@@ -1,5 +1,5 @@
import { AppRootState } from '.';
import type { Proof } from '../utils/types/types';
import type { AttestedData } from '../utils/types/types';
import { useSelector } from 'react-redux';
export enum ActionType {
@@ -8,7 +8,7 @@ export enum ActionType {
UploadFileSuccess = 'proofupload/uploadFileSuccess',
}
export const uploadFile = (fileName: string, proof: Proof) => ({
export const uploadFile = (fileName: string, proof: AttestedData) => ({
type: ActionType.AddFile,
payload: { fileName, proof },
});
@@ -31,8 +31,12 @@ export type Action<payload = any> = {
};
type State = {
proofs: { fileName: string; proof: Proof }[];
selectedProof?: { fileName: string; proof: Proof; ipfsCID?: string } | null;
proofs: { fileName: string; proof: AttestedData }[];
selectedProof?: {
fileName: string;
proof: AttestedData;
ipfsCID?: string;
} | null;
};
const initState: State = {

View File

@@ -1,5 +1,5 @@
import React, { ReactElement, useRef } from 'react';
import { Attestation, Proof } from './types/types';
import { Attestation, AttestedData } from './types/types';
export const readFileAsync = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
@@ -98,36 +98,50 @@ async function initTlsnJs() {
export async function verify(
attestation: Attestation,
pubKey: string,
): Promise<Proof> {
): Promise<AttestedData> {
let key = pubKey;
const { NotaryServer } = await import('tlsn-js');
console.log(attestation.version)
await initTlsnJs();
switch (attestation.version) {
case undefined: {
const { verify } = await import('tlsn-js-v5');
key = key || (await NotaryServer.from(attestation.notaryUrl).publicKey());
return await verify(attestation, key);
key =
key ||
(await NotaryServer.from(attestation.notaryUrl).publicKey('pem'));
const data = await verify(attestation, key);
return {
...data,
version: '0.1.0-alpha.5',
notaryUrl: attestation.notaryUrl,
notaryKey: key,
};
}
case '0.1.0-alpha.7': {
const { Presentation, Transcript } = await import('tlsn-js');
console.log(Buffer.from(attestation.data, 'hex').toString('binary'));
await initTlsnJs();
key =
key ||
(await NotaryServer.from(attestation.meta.notaryUrl).publicKey());
const tlsProof = new Presentation(attestation.data);
console.log(tlsProof);
const data = await tlsProof.verify();
const transcript = new Transcript({
sent: data.transcript.sent,
recv: data.transcript.recv,
})
const vk = tlsProof.verifyingKey();
});
const vk = await tlsProof.verifyingKey();
const verifyingKey = Buffer.from(vk.data).toString('hex');
const notaryUrl = convertNotaryWsToHttp(attestation.meta.notaryUrl);
const publicKey = await new NotaryServer(notaryUrl).publicKey();
if (verifyingKey !== publicKey)
throw new Error(`Notary key doesn't match verifying key`);
return {
version: '0.1.0-alpha.7',
sent: transcript.sent(),
recv: transcript.recv(),
time: data.connection_info.time,
notaryUrl: attestation.meta.notaryUrl,
notaryUrl: notaryUrl,
notaryKey: publicKey,
websocketProxyUrl: attestation.meta.websocketProxyUrl,
verifierKey: verifyingKey,
};
}
}
@@ -135,6 +149,13 @@ export async function verify(
throw new Error('Invalid Proof');
}
export function convertNotaryWsToHttp(notaryWs: string) {
const { protocol, pathname, hostname } = new URL(notaryWs);
const p = protocol === 'wss:' ? 'https:' : 'http';
const path = pathname === '/' ? '' : pathname.replace('/notarize', '');
return p + '//' + hostname + path;
}
function defer(): {
promise: Promise<any>;
resolve: (args?: any) => any;

View File

@@ -1,8 +1,12 @@
export interface Proof {
export interface AttestedData {
version: '0.1.0-alpha.7' | '0.1.0-alpha.5';
time: number;
sent: string;
recv: string;
notaryUrl: string;
notaryKey: string;
websocketProxyUrl?: string;
verifierKey?: string;
}
export type AttestationV0 = {
@@ -13,7 +17,7 @@ export type AttestationV0 = {
};
export type AttestationV1 = {
version: '0.1.0-alpha.7' | '0.1.0-alpha.6';
version: '0.1.0-alpha.7';
data: string;
meta: {
notaryUrl: string;