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

feat: verify file proof #4

Merged
merged 3 commits into from
Feb 25, 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
7 changes: 0 additions & 7 deletions app/components/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
import { useRouter } from 'next/navigation';
import React, { FC } from 'react';
import {
IoCheckmarkCircleOutline,
IoChevronBackOutline,
IoCloudUploadOutline,
IoListOutline,
Expand All @@ -31,7 +30,6 @@ import {
FILES_ROUTE,
INDEX_ROUTE,
UPLOAD_ROUTE,
VERIFY_ROUTE,
} from '@app/constants';

// hooks
Expand Down Expand Up @@ -60,11 +58,6 @@ const Navigation: FC<IProps> = ({ isOpen, onClose }) => {
label: 'Files',
route: FILES_ROUTE,
},
{
icon: IoCheckmarkCircleOutline,
label: 'Verify',
route: VERIFY_ROUTE,
},
];
// handlers
const handleOnClose = () => onClose();
Expand Down
1 change: 1 addition & 0 deletions app/components/UploadCompleteModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './UploadCompleteModal';
204 changes: 204 additions & 0 deletions app/components/VerifyFileProofModal/VerifyFileProofModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import {
Button,
Code,
Heading,
HStack,
Icon,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Spacer,
Text,
Textarea,
VStack,
} from '@chakra-ui/react';
import React, { ChangeEvent, FC, useEffect, useState } from 'react';
import {
IoCheckmarkCircleOutline,
IoCloseCircleOutline,
} from 'react-icons/io5';

// constants
import { DEFAULT_GAP } from '@app/constants';

// hooks
import useDefaultTextColor from '@app/hooks/useDefaultTextColor';
import usePrimaryColorScheme from '@app/hooks/usePrimaryColorScheme';
import useSubTextColor from '@app/hooks/useSubTextColor';

// types
import type { IProps } from './types';

// utils
import verifyMerkleProof from '@app/utils/verifyMerkleProof';

const VerifyFileProofModal: FC<IProps> = ({ file, onClose }) => {
// hooks
const defaultTextColor: string = useDefaultTextColor();
const primaryColorScheme: string = usePrimaryColorScheme();
const subTextColor: string = useSubTextColor();
// state
const [rootValue, setRootValue] = useState<string>('');
const [verified, setVerified] = useState<boolean>(false);
// reset
const reset = () => {
setRootValue('');
setVerified(false);
};
// handlers
const handleOnClose = () => {
reset();
onClose();
};
const handleRootValueChange = async (
event: ChangeEvent<HTMLTextAreaElement>
) => setRootValue(event.target.value);

useEffect(() => {
(async () => {
let isValidRoot: boolean = verified;

if (file) {
isValidRoot = await verifyMerkleProof(rootValue, file.proof);
}

setVerified(isValidRoot);
})();
}, [rootValue]);

return (
<Modal closeOnOverlayClick={false} isOpen={!!file} onClose={onClose}>
<ModalOverlay />

<ModalContent>
<ModalHeader>
<Heading color={defaultTextColor} size="md" textAlign="center">
{`Verify File Proof`}
</Heading>
</ModalHeader>

<ModalBody>
<VStack spacing={DEFAULT_GAP} w="full">
{/*description*/}
<Text color={defaultTextColor} textAlign="left" w="full">
{`Here is the Merkle proof for the file.`}
</Text>

{/*file name*/}
<HStack
alignItems="center"
justifyContent="space-between"
spacing={2}
w="full"
>
<Text color={defaultTextColor} fontSize="sm" textAlign="left">
{`Name:`}
</Text>

<Spacer />

<Text color={subTextColor} fontSize="sm" textAlign="right">
{file ? file.name : '-'}
</Text>
</HStack>

{/*hash*/}
<HStack
alignItems="center"
justifyContent="space-between"
spacing={2}
w="full"
>
<Text color={defaultTextColor} fontSize="sm" textAlign="left">
{`Hash:`}
</Text>

<Spacer />

<Code
fontSize="sm"
textAlign="left"
w="full"
wordBreak="break-word"
>
{file ? file.hash : '-'}
</Code>
</HStack>

{/*proof*/}
<Code fontSize="sm" textAlign="left" w="full">
{file ? JSON.stringify(file.proof) : '-'}
</Code>

<Text color={defaultTextColor} textAlign="left" w="full">
{`Enter the Merkle Root and see of the proof is verifiable:`}
</Text>

{/*root value input*/}
<Textarea onChange={handleRootValueChange} value={rootValue} />

{/*verified status*/}
<HStack
alignItems="center"
justifyContent="center"
minH={50}
spacing={2}
w="full"
>
{rootValue ? (
verified ? (
<>
<Icon
as={IoCheckmarkCircleOutline}
color="green.500"
h={6}
w={6}
/>

<Text
color="green.500"
textAlign="left"
>{`Proof Valid`}</Text>
</>
) : (
<>
<Icon
as={IoCloseCircleOutline}
color="red.500"
h={6}
w={6}
/>

<Text
color="red.500"
textAlign="left"
>{`Proof Invalid`}</Text>
</>
)
) : null}
</HStack>
</VStack>
</ModalBody>

<ModalFooter>
<HStack justifyContent="flex-end" spacing={2} w="full">
<Button
colorScheme={primaryColorScheme}
onClick={handleOnClose}
variant="solid"
>
{`Cancel`}
</Button>
</HStack>
</ModalFooter>
</ModalContent>
</Modal>
);
};

VerifyFileProofModal.displayName = 'VerifyFileProofModal';

export default VerifyFileProofModal;
1 change: 1 addition & 0 deletions app/components/VerifyFileProofModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './VerifyFileProofModal';
9 changes: 9 additions & 0 deletions app/components/VerifyFileProofModal/types/IProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// types
import type { IFileResponse } from '@app/types';

interface IProps {
file: IFileResponse | null;
onClose: () => void;
}

export default IProps;
1 change: 1 addition & 0 deletions app/components/VerifyFileProofModal/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { default as IProps } from './IProps';
1 change: 0 additions & 1 deletion app/constants/Routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const FILES_ROUTE: string = '/files';
export const INDEX_ROUTE: string = '/';
export const UPLOAD_ROUTE: string = '/upload';
export const VERIFY_ROUTE: string = '/verify';
72 changes: 49 additions & 23 deletions app/files/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ import {
VStack,
} from '@chakra-ui/react';
import { NextPage } from 'next';
import React from 'react';
import { IoDownloadOutline } from 'react-icons/io5';
import React, { useState } from 'react';
import { IoCheckmarkDoneOutline, IoDownloadOutline } from 'react-icons/io5';

// components
import CopyIconButton from '@app/components/CopyIconButton';
import VerifyFileProofModal from '@app/components/VerifyFileProofModal';

// constants
import { DEFAULT_GAP } from '@app/constants';
Expand All @@ -44,9 +45,14 @@ const FilesPage: NextPage = () => {
const defaultTextColor: string = useDefaultTextColor();
const subTextColor: string = useSubTextColor();
const { files, loading } = useFiles();
// state
const [fileToVerify, setFileToVerify] = useState<IFileResponse | null>(null);
// handlers
const handleDownloadProofClick = (file: IFileResponse) => () =>
downloadJSONFile(file.hash, file.proof);
const handleVerifyFileProofClick = (file: IFileResponse) => () =>
setFileToVerify(file);
const handleVerifyFileProofModalClose = () => setFileToVerify(null);
// renders
const renderContent = () => {
let fileKeys: string[];
Expand All @@ -62,7 +68,7 @@ const FilesPage: NextPage = () => {

if (fileKeys.length > 0) {
return (
<Accordion allowMultiple={true} allowToggle={true} w="full">
<Accordion allowMultiple={true} w="full">
{fileKeys.map((key, fileKeyIndex) => (
<AccordionItem key={`files-page-${key}-${fileKeyIndex}`}>
{/*accordian button*/}
Expand Down Expand Up @@ -125,6 +131,19 @@ const FilesPage: NextPage = () => {
variant="ghost"
/>
</Tooltip>

{/*verify proof*/}
<Tooltip label={`Verify Proof`}>
<IconButton
_hover={{ bg: buttonHoverBackgroundColor }}
aria-label="Verify proof"
color={defaultTextColor}
icon={<IoCheckmarkDoneOutline />}
onClick={handleVerifyFileProofClick(file)}
size="md"
variant="ghost"
/>
</Tooltip>
</HStack>
))}
</AccordionPanel>
Expand All @@ -146,33 +165,40 @@ const FilesPage: NextPage = () => {
};

return (
<VStack
alignItems="center"
justifyContent="flex-start"
flexGrow={1}
spacing={DEFAULT_GAP}
w="full"
>
{/*heading*/}
<Heading color={defaultTextColor} size="lg" textAlign="center" w="full">
{`Files`}
</Heading>

{/*description*/}
<Text color={defaultTextColor} size="md" textAlign="center" w="full">
{`Below is a list of files, grouped by their merkle tree roots. You can download a file's proof and use the root you received to verify the file's integrity.`}
</Text>
<>
<VerifyFileProofModal
file={fileToVerify}
onClose={handleVerifyFileProofModalClose}
/>

<VStack
alignItems="center"
justifyContent="flex-start"
flexGrow={1}
justify="flex-start"
spacing={DEFAULT_GAP - 2}
spacing={DEFAULT_GAP}
w="full"
>
{renderContent()}
{/*heading*/}
<Heading color={defaultTextColor} size="lg" textAlign="center" w="full">
{`Files`}
</Heading>

{/*description*/}
<Text color={defaultTextColor} size="md" textAlign="center" w="full">
{`Below is a list of files, grouped by their merkle tree roots. You can download a file's proof and use the root you received to verify the file's integrity.`}
</Text>

<VStack
alignItems="center"
flexGrow={1}
justify="flex-start"
spacing={DEFAULT_GAP - 2}
w="full"
>
{renderContent()}
</VStack>
</VStack>
</VStack>
</>
);
};

Expand Down
2 changes: 1 addition & 1 deletion app/upload/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import React, { ChangeEvent, MutableRefObject, useRef, useState } from 'react';
import { IoCloudUploadOutline, IoDocumentsOutline } from 'react-icons/io5';

// components
import UploadCompleteModal from '@app/components/UploadCompleteModal/UploadCompleteModal';
import UploadCompleteModal from '@app/components/UploadCompleteModal';

// constants
import { DEFAULT_GAP, FILES_PATH, UPLOAD_PATH } from '@app/constants';
Expand Down
1 change: 1 addition & 0 deletions app/utils/sha256/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './sha256';
Loading
Loading