diff --git a/src/components/KeyManager.tsx b/src/components/KeyManager.tsx index d87bd67..ec31c2e 100644 --- a/src/components/KeyManager.tsx +++ b/src/components/KeyManager.tsx @@ -31,6 +31,7 @@ import { IconKey, IconPlus, IconTrash, + IconWritingSign, } from '@tabler/icons-react'; import useConfirmation from '../hooks/useConfirmation'; @@ -64,6 +65,7 @@ import ImportKeyFromLedgerModal from './ImportKeyFromLedgerModal'; import ImportKeyPairModal from './ImportKeyPairModal'; import RenameKeyModal from './RenameKeyModal'; import RevealSecretKeyModal from './RevealSecretKeyModal'; +import SignMessageModal from './SignMessageModal'; type KeyManagerProps = { isOpen: boolean; @@ -117,9 +119,11 @@ function KeyManager({ isOpen, onClose }: KeyManagerProps): JSX.Element { const createAccountModal = useDisclosure(); const importAccountModal = useDisclosure(); const editAccountModal = useDisclosure(); + const signMessageModal = useDisclosure(); const [renameKeyIdx, setRenameKeyIdx] = useState(0); const [editAccountIdx, setEditAccountIdx] = useState(0); + const [signMessageByKeyIndex, setSignMessageByKeyIndex] = useState(0); const closeHandler = () => { onClose(); @@ -241,59 +245,91 @@ function KeyManager({ isOpen, onClose }: KeyManagerProps): JSX.Element { backgroundColor="blackAlpha.300" borderRadius="md" > - {key.type === KeyPairType.Software ? ( - - ) : ( - - - Hardware - - )} - + + {key.type === KeyPairType.Software ? ( + <> + + + + ) : ( + + + Hardware + + )} + + {key.displayName} - onRenameKey(idx)} - icon={} - variant="whiteOutline" - borderWidth={1} - size="xs" - /> - onDeleteKey(idx)} - icon={} - variant="dangerOutline" - borderWidth={1} - size="xs" - /> + + onRenameKey(idx)} + icon={} + variant="whiteOutline" + borderWidth={1} + size="xs" + mr={1} + /> + onDeleteKey(idx)} + icon={} + variant="dangerOutline" + borderWidth={1} + size="xs" + mr={1} + /> + Public Key @@ -483,6 +519,11 @@ function KeyManager({ isOpen, onClose }: KeyManagerProps): JSX.Element { isOpen={editAccountModal.isOpen} onClose={editAccountModal.onClose} /> + diff --git a/src/components/MainMenu.tsx b/src/components/MainMenu.tsx index c718078..7f998db 100644 --- a/src/components/MainMenu.tsx +++ b/src/components/MainMenu.tsx @@ -16,6 +16,7 @@ import { MAIN_MENU_BUTTONS_SIZE } from '../utils/constants'; import KeyManager from './KeyManager'; import MnemonicsModal from './MnemonicsModal'; +import VerifyMessageModal from './VerifyMessageModal'; import WipeOutAlert from './WipeOutAlert'; function MainMenu(): JSX.Element { @@ -24,6 +25,7 @@ function MainMenu(): JSX.Element { const wipeAlert = useDisclosure(); const keyManagerDrawer = useDisclosure(); + const verifyMessageModal = useDisclosure(); const { revealMnemonics } = useMnemonics(); const iconSize = useBreakpointValue(MAIN_MENU_BUTTONS_SIZE, { ssr: false }); @@ -46,6 +48,10 @@ function MainMenu(): JSX.Element { Backup mnemonic Export wallet file + + Verify Message + + Wipe out @@ -57,6 +63,10 @@ function MainMenu(): JSX.Element { isOpen={keyManagerDrawer.isOpen} onClose={keyManagerDrawer.onClose} /> + ); } diff --git a/src/components/SignMessage.tsx b/src/components/SignMessage.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/SignMessageModal.tsx b/src/components/SignMessageModal.tsx new file mode 100644 index 0000000..7c269eb --- /dev/null +++ b/src/components/SignMessageModal.tsx @@ -0,0 +1,175 @@ +import fileDownload from 'js-file-download'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; + +import { + Button, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Spacer, + Text, + Textarea, +} from '@chakra-ui/react'; + +import useCopy from '../hooks/useCopy'; +import { useSignMessage } from '../hooks/useSigning'; +import usePassword from '../store/usePassword'; +import useWallet from '../store/useWallet'; +import { SignedMessage } from '../types/message'; +import { KeyPairType } from '../types/wallet'; +import { SIGNED_MESSAGE_PREFIX } from '../utils/constants'; +import { toHexString } from '../utils/hexString'; + +type SignMessageModalProps = { + keyIndex: number; + isOpen: boolean; + onClose: () => void; +}; + +function SignMessageModal({ + keyIndex, + isOpen, + onClose, +}: SignMessageModalProps): JSX.Element | null { + const { wallet } = useWallet(); + const signMessage = useSignMessage(); + const { withPassword } = usePassword(); + const { register, reset, handleSubmit } = useForm<{ message: string }>(); + const [signResult, setSignResult] = useState(''); + const { isCopied, onCopy } = useCopy(); + + const keys = wallet?.keychain ?? []; + const key = keys[keyIndex]; + if (!key) return null; + + const close = () => { + setSignResult(''); + reset(); + onClose(); + }; + + const download = () => + fileDownload(signResult, `signed-message.json`, 'plain/text'); + + const submit = handleSubmit(async ({ message }) => { + const result = await withPassword( + async (password) => { + if (key.type === KeyPairType.Hardware) { + // Sign using Ledger device + throw new Error('Hardware wallet is not supported yet'); + } + const text = `${SIGNED_MESSAGE_PREFIX}${message}`; + // Sign using local key + return JSON.stringify( + { + publicKey: key.publicKey, + text, + signature: toHexString( + await signMessage(text, key.publicKey, password) + ), + } satisfies SignedMessage, + null, + 2 + ); + }, + 'Sign message', + <> + Please enter your password to sign the message using key " + {key.displayName}"{' '} + + ({key.publicKey}) + + + ); + if (result) { + setSignResult(result); + } else { + setSignResult(''); + } + }); + + return ( + + + + + Sign message + {signResult ? ( + <> + + + Here is your message and the signature. You can copy them to the + clipboard and share with another party. + +