From cbeaddb5629f7da8e2489179b6f0b89db53a1f11 Mon Sep 17 00:00:00 2001 From: dillon Date: Fri, 14 Jul 2023 20:43:13 -0400 Subject: [PATCH] separate factory reset flow --- src/components/L2/Window/BodyPane.scss | 9 + src/lib/usetSetNetworkKeys.ts | 115 ++++++++++ src/style/shared/variables.scss | 1 + src/views/Point/UrbitOS.jsx | 3 + src/views/UrbitOS/FactoryReset.scss | 59 +++++ src/views/UrbitOS/FactoryReset.tsx | 285 +++++++++++++++++++++++++ src/views/UrbitOS/Home.tsx | 35 ++- src/views/UrbitOS/NetworkKeys.tsx | 176 +-------------- src/worker/index.ts | 3 +- 9 files changed, 512 insertions(+), 174 deletions(-) create mode 100644 src/lib/usetSetNetworkKeys.ts create mode 100644 src/views/UrbitOS/FactoryReset.scss create mode 100644 src/views/UrbitOS/FactoryReset.tsx diff --git a/src/components/L2/Window/BodyPane.scss b/src/components/L2/Window/BodyPane.scss index bdce3a016..65adfd228 100644 --- a/src/components/L2/Window/BodyPane.scss +++ b/src/components/L2/Window/BodyPane.scss @@ -26,6 +26,9 @@ &.error-text { color: $warning-red; } + &.maxwidth { + max-width: 300px; + } } .arrow-button { @@ -67,6 +70,12 @@ &:hover { opacity: 0.8; } + + &.warning { + color: $warning-red; + background-color: $warning-red-bg-tsp; + min-width: 75px; + } } .copy-button { diff --git a/src/lib/usetSetNetworkKeys.ts b/src/lib/usetSetNetworkKeys.ts new file mode 100644 index 000000000..b02ab79e2 --- /dev/null +++ b/src/lib/usetSetNetworkKeys.ts @@ -0,0 +1,115 @@ +import { useCallback, useRef } from 'react'; +import { Just } from 'folktale/maybe'; +import * as azimuth from 'azimuth-js'; +import { randomHex } from 'web3-utils'; + +import { usePointCache } from 'store/pointCache'; +import { useNetwork } from 'store/network'; +import { useWallet } from 'store/wallet'; +import { useRollerStore } from 'store/rollerStore'; + +import * as need from 'lib/need'; +import { + attemptNetworkSeedDerivation, + deriveNetworkKeys, + CRYPTO_SUITE_VERSION, +} from 'lib/keys'; +import useEthereumTransaction from 'lib/useEthereumTransaction'; +import { GAS_LIMITS } from 'lib/constants'; +import { addHexPrefix } from 'lib/utils/address'; +import { useSingleKeyfileGenerator } from 'lib/useKeyfileGenerator'; + +export function useSetNetworkKeys( + manualNetworkSeed: string, + setManualNetworkSeed: (seed: string) => void +) { + const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet(); + const { point } = useRollerStore(); + const { syncDetails, syncRekeyDate }: any = usePointCache(); + const { contracts }: any = useNetwork(); + + const _contracts = need.contracts(contracts); + + const networkRevision = Number(point.keyRevisionNumber); + const randomSeed = useRef(); + + const { + generating: keyfileGenerating, + filename, + bind: keyfileBind, + } = useSingleKeyfileGenerator({ seed: manualNetworkSeed }); + + const buildNetworkSeed = useCallback( + async manualSeed => { + if (manualSeed !== undefined) { + setManualNetworkSeed(manualSeed); + return manualSeed; + } else { + const newNetworkRevision = networkRevision + 1; + console.log(`deriving seed with revision ${newNetworkRevision}`); + + const networkSeed = await attemptNetworkSeedDerivation({ + urbitWallet, + wallet, + authMnemonic, + details: point, + point: point.value, + authToken, + revision: newNetworkRevision, + }); + + if (Just.hasInstance(networkSeed)) { + return networkSeed.value; + } + + randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes + setManualNetworkSeed(randomSeed.current); + + return randomSeed.current; + } + }, + [ + authMnemonic, + setManualNetworkSeed, + networkRevision, + urbitWallet, + wallet, + point, + authToken, + ] + ); + + const { completed: _completed, ...rest } = useEthereumTransaction( + useCallback( + async (manualSeed: string, isDiscontinuity: boolean) => { + const seed = await buildNetworkSeed(manualSeed); + const pair = deriveNetworkKeys(seed); + + return azimuth.ecliptic.configureKeys( + _contracts, + point.value, + addHexPrefix(pair.crypt.public), + addHexPrefix(pair.auth.public), + CRYPTO_SUITE_VERSION, + isDiscontinuity + ); + }, + [_contracts, point, buildNetworkSeed] + ), + useCallback( + () => Promise.all([syncDetails(point.value), syncRekeyDate(point.value)]), + [point, syncDetails, syncRekeyDate] + ), + GAS_LIMITS.CONFIGURE_KEYS + ); + + // only treat the transaction as completed once we also have keys to download + const completed = _completed && !keyfileGenerating; + + return { + completed, + filename, + keyfileBind, + ...rest, + }; +} diff --git a/src/style/shared/variables.scss b/src/style/shared/variables.scss index efa6b256b..38dd446e3 100644 --- a/src/style/shared/variables.scss +++ b/src/style/shared/variables.scss @@ -10,6 +10,7 @@ $dark-gray: rgba(102, 102, 102, 1); $rollup-yellow: rgba(255, 199, 0, 1); $accept-green: rgba(0, 159, 101, 1); $warning-red: rgb(242, 33, 33); +$warning-red-bg-tsp: rgba(242, 33, 33, 0.15); $light-gray-border: 1px solid $light-gray; $activate-blue: rgba(33, 157, 255, 1); $light-blue: rgba(33, 157, 255, 0.3); diff --git a/src/views/Point/UrbitOS.jsx b/src/views/Point/UrbitOS.jsx index 3fa5b307c..6ed17940b 100644 --- a/src/views/Point/UrbitOS.jsx +++ b/src/views/Point/UrbitOS.jsx @@ -11,17 +11,20 @@ import L2BackHeader from 'components/L2/Headers/L2BackHeader'; import UrbitOSHome from '../UrbitOS/Home'; import UrbitOSNetworkKeys from '../UrbitOS/NetworkKeys.tsx'; import UrbitOSChangeSponsor from '../UrbitOS/ChangeSponsor.tsx'; +import UrbitOSFactoryReset from '../UrbitOS/FactoryReset.tsx'; const NAMES = { HOME: 'HOME', CHANGE_SPONSOR: 'CHANGE_SPONSOR', NETWORKING_KEYS: 'NETWORKING_KEYS', + FACTORY_RESET: 'FACTORY_RESET', }; const VIEWS = { [NAMES.HOME]: UrbitOSHome, [NAMES.CHANGE_SPONSOR]: UrbitOSChangeSponsor, [NAMES.NETWORKING_KEYS]: UrbitOSNetworkKeys, + [NAMES.FACTORY_RESET]: UrbitOSFactoryReset, }; export default function UrbitOS() { diff --git a/src/views/UrbitOS/FactoryReset.scss b/src/views/UrbitOS/FactoryReset.scss new file mode 100644 index 000000000..eeb5adb70 --- /dev/null +++ b/src/views/UrbitOS/FactoryReset.scss @@ -0,0 +1,59 @@ +@import 'style/shared/variables.scss'; + +.network-keys { + font-size: 14px; + + .body-pane { + height: 360px; + + .download { + height: 24px; + width: 24px; + margin: 0 auto 22px; + } + + .download-message { + align-items: center; + width: 350px; + //styleName: Sans/UI + Control; + font-family: Inter; + line-height: 16px; + letter-spacing: 0em; + text-align: center; + margin: 90px auto 40px; + } + + .contents { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + + .check-row { + margin-top: 8px; + font-weight: 500; + + .checkbox { + border-radius: $medium-border-radius; + height: 16px; + width: 16px; + margin-right: 14px; + + svg { + height: 18px; + width: 18px; + } + } + } + + .info-text { + color: $dark-gray; + margin: 8px 0px 24px 32px; + } + } + } + .center > span { + margin: auto; + } +} diff --git a/src/views/UrbitOS/FactoryReset.tsx b/src/views/UrbitOS/FactoryReset.tsx new file mode 100644 index 000000000..b0916d1e5 --- /dev/null +++ b/src/views/UrbitOS/FactoryReset.tsx @@ -0,0 +1,285 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Grid, Button } from 'indigo-react'; +import { Box, Checkbox, Icon, Row, Text } from '@tlon/indigo-react'; + +import { useRollerStore } from 'store/rollerStore'; + +import useRoller from 'lib/useRoller'; +import { useLocalRouter } from 'lib/LocalRouter'; +import { ONE_SECOND } from 'lib/constants'; + +import DownloadKeyfileButton from 'components/DownloadKeyfileButton'; +import InlineEthereumTransaction from 'components/InlineEthereumTransaction'; +import NoticeBox from 'components/NoticeBox'; +import WarningBox from 'components/WarningBox'; + +import { HexInput } from 'form/Inputs'; +import { + composeValidator, + buildCheckboxValidator, + buildHexValidator, +} from 'form/validators'; +import BridgeForm from 'form/BridgeForm'; +import FormError from 'form/FormError'; +import Window from 'components/L2/Window/Window'; +import HeaderPane from 'components/L2/Window/HeaderPane'; +import BodyPane from 'components/L2/Window/BodyPane'; + +import './FactoryReset.scss'; +import { L1TxnType } from 'lib/types/PendingL1Transaction'; +import { PointField } from 'lib/types/Point'; +import { useSetNetworkKeys } from 'lib/usetSetNetworkKeys'; + +export default function UrbitOSFactoryReset({ + manualNetworkSeed, + setManualNetworkSeed, +}: { + manualNetworkSeed: string; + setManualNetworkSeed: (seed: string) => void; +}) { + const { pop }: any = useLocalRouter(); + const { point, setLoading } = useRollerStore(); + const { + configureNetworkKeys, + getPendingTransactions, + checkForUpdates, + } = useRoller(); + const [useCustomSeed, setUseCustomSeed] = useState(false); + const [l2Completed, setL2Completed] = useState(false); + const { + construct, + unconstruct, + completed, + inputsLocked, + bind, + keyfileBind, + txHashes, + } = useSetNetworkKeys(manualNetworkSeed, setManualNetworkSeed); + + useEffect(() => { + if (completed) { + checkForUpdates({ + point: point, + field: PointField.keyRevisionNumber, + message: `${point.patp}'s Network Keys have been set!`, + l1Txn: { + id: `${point.keyRevisionNumber}-network-keys-${point.value}`, + point: point.value, + type: L1TxnType.setNetworkKeys, + hash: txHashes[0], + time: new Date().getTime(), + }, + intervalTime: ONE_SECOND, + }); + } + }, [completed]); // eslint-disable-line react-hooks/exhaustive-deps + + const validateForm = useCallback((values, errors) => { + if (values.useNetworkSeed && errors.networkSeed) { + return errors; + } + }, []); + + const validate = useMemo( + () => + composeValidator( + { + useNetworkSeed: buildCheckboxValidator(), + networkSeed: buildHexValidator(64), // 64 chars + useDiscontinuity: buildCheckboxValidator(), + }, + validateForm + ), + [validateForm] + ); + + const onValues = useCallback( + ({ valid, values, form }) => { + console.log(valid); + if (valid) { + const networkSeed = useCustomSeed ? values.networkSeed : undefined; + construct(networkSeed, true); + setManualNetworkSeed(networkSeed); + } else { + unconstruct(); + } + + if (!useCustomSeed && values.networkSeed) { + form.change('networkSeed', ''); + setManualNetworkSeed(''); + } + }, + [construct, unconstruct, setManualNetworkSeed, useCustomSeed] + ); + + const setNetworkKeys = useCallback(async () => { + setLoading(true); + try { + console.log('breaching'); + await configureNetworkKeys({ + breach: true, + customNetworkSeed: manualNetworkSeed, + }); + // TODO: just use the tx hash instead? + getPendingTransactions(); + // TODO: this is just so we get visual feedback that the tx has gone through + // but this should be addressed with the new UI flow (-> download keyfile) + // + // A question here is how to deal with the modals/messages about the keys not being + // set since they are in pending in the Roller... + // we could inspect if there's a changeKeys in the local list of pending txs,... + // + checkForUpdates({ + point: point, + message: `${point.patp}'s Network Keys have been set!`, + intervalTime: ONE_SECOND, + }); + setTimeout(() => setL2Completed(true), ONE_SECOND); + } catch (error) { + // setError(error); + } finally { + setLoading(false); + } + }, [ + manualNetworkSeed, + getPendingTransactions, + point, + configureNetworkKeys, + setLoading, + checkForUpdates, + ]); + + const initialValues = useMemo(() => ({}), []); + + const viewTitle = completed + ? 'Network Keys have been set' + : 'Factory Reset (Breach)'; + + const bootMessage = () => ( + + Download your new Network Key to boot your ship. Be sure to delete your + old pier before booting with this new key. + + ); + + const waitMessage = () => ( + + Important: you will need to wait until the next L2 batch + is submitted to the roller to boot your ship. If you do not wait, then you + will not be able to boot your ship. + + ); + + const buttonText = 'Factory Reset (Breach)'; + + const isComplete = completed || l2Completed; + + return ( + + +
{viewTitle}
+
+ + {isComplete && ( + + + + {bootMessage()} + {point.isL2 ? waitMessage() : null} + + {point.isL2 ? ( + + Download Network Key + + ) : ( + + )} + + )} + + {!isComplete && ( + + {() => ( + + {!isComplete && ( + + setUseCustomSeed(!useCustomSeed)}> + + Use Custom Network Seed (optional) + + + Enter your own network seed from which to derive the keys + + {useCustomSeed && ( + <> + + When using a custom network seed, you'll need to + download your Arvo keyfile immediately after this + transaction as Bridge does not store your seed. + + + + )} + + + )} + + + + Beware, this erases all of your ship's data. + + {point.isL2 ? ( + + {buttonText} + + ) : ( + pop()} + /> + )} + + + )} + + )} + +
+ ); +} diff --git a/src/views/UrbitOS/Home.tsx b/src/views/UrbitOS/Home.tsx index 77cc566ee..400c447e4 100644 --- a/src/views/UrbitOS/Home.tsx +++ b/src/views/UrbitOS/Home.tsx @@ -38,6 +38,11 @@ export default function UrbitOSHome() { push, ]); + const goFactoryReset = useCallback(() => push(names.FACTORY_RESET), [ + names, + push, + ]); + const goChangeSponsor = useCallback(() => push(names.CHANGE_SPONSOR), [ push, names, @@ -115,6 +120,19 @@ export default function UrbitOSHome() { )} + + {!!code && ( + + + Access Key + Your passkey to login to Landscape + + + + + + )} + Network Keys @@ -129,7 +147,7 @@ export default function UrbitOSHome() { {hasSetNetworkKeys && ( )} diff --git a/src/views/UrbitOS/NetworkKeys.tsx b/src/views/UrbitOS/NetworkKeys.tsx index 44e2b50c9..25279033e 100644 --- a/src/views/UrbitOS/NetworkKeys.tsx +++ b/src/views/UrbitOS/NetworkKeys.tsx @@ -1,33 +1,12 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { Just } from 'folktale/maybe'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Grid, Button } from 'indigo-react'; -import * as azimuth from 'azimuth-js'; -import { randomHex } from 'web3-utils'; import { Box, Checkbox, Icon, Row, Text } from '@tlon/indigo-react'; -import { usePointCache } from 'store/pointCache'; -import { useNetwork } from 'store/network'; -import { useWallet } from 'store/wallet'; import { useRollerStore } from 'store/rollerStore'; import useRoller from 'lib/useRoller'; import { useLocalRouter } from 'lib/LocalRouter'; -import * as need from 'lib/need'; -import { - attemptNetworkSeedDerivation, - deriveNetworkKeys, - CRYPTO_SUITE_VERSION, -} from 'lib/keys'; -import useEthereumTransaction from 'lib/useEthereumTransaction'; -import { GAS_LIMITS, ONE_SECOND } from 'lib/constants'; -import { addHexPrefix } from 'lib/utils/address'; -import { useSingleKeyfileGenerator } from 'lib/useKeyfileGenerator'; +import { ONE_SECOND } from 'lib/constants'; import DownloadKeyfileButton from 'components/DownloadKeyfileButton'; import InlineEthereumTransaction from 'components/InlineEthereumTransaction'; @@ -48,101 +27,7 @@ import BodyPane from 'components/L2/Window/BodyPane'; import './NetworkKeys.scss'; import { L1TxnType } from 'lib/types/PendingL1Transaction'; import { PointField } from 'lib/types/Point'; - -function useSetKeys( - manualNetworkSeed: string, - setManualNetworkSeed: (seed: string) => void -) { - const { urbitWallet, wallet, authMnemonic, authToken }: any = useWallet(); - const { point } = useRollerStore(); - const { syncDetails, syncRekeyDate }: any = usePointCache(); - const { contracts }: any = useNetwork(); - - const _contracts = need.contracts(contracts); - - const networkRevision = Number(point.keyRevisionNumber); - const randomSeed = useRef(); - - const { - generating: keyfileGenerating, - filename, - bind: keyfileBind, - } = useSingleKeyfileGenerator({ seed: manualNetworkSeed }); - - const buildNetworkSeed = useCallback( - async manualSeed => { - if (manualSeed !== undefined) { - setManualNetworkSeed(manualSeed); - return manualSeed; - } else { - const newNetworkRevision = networkRevision + 1; - console.log(`deriving seed with revision ${newNetworkRevision}`); - - const networkSeed = await attemptNetworkSeedDerivation({ - urbitWallet, - wallet, - authMnemonic, - details: point, - point: point.value, - authToken, - revision: newNetworkRevision, - }); - - if (Just.hasInstance(networkSeed)) { - return networkSeed.value; - } - - randomSeed.current = randomSeed.current || randomHex(32); // 32 bytes - setManualNetworkSeed(randomSeed.current); - - return randomSeed.current; - } - }, - [ - authMnemonic, - setManualNetworkSeed, - networkRevision, - urbitWallet, - wallet, - point, - authToken, - ] - ); - - const { completed: _completed, ...rest } = useEthereumTransaction( - useCallback( - async (manualSeed: string, isDiscontinuity: boolean) => { - const seed = await buildNetworkSeed(manualSeed); - const pair = deriveNetworkKeys(seed); - - return azimuth.ecliptic.configureKeys( - _contracts, - point.value, - addHexPrefix(pair.crypt.public), - addHexPrefix(pair.auth.public), - CRYPTO_SUITE_VERSION, - isDiscontinuity - ); - }, - [_contracts, point, buildNetworkSeed] - ), - useCallback( - () => Promise.all([syncDetails(point.value), syncRekeyDate(point.value)]), - [point, syncDetails, syncRekeyDate] - ), - GAS_LIMITS.CONFIGURE_KEYS - ); - - // only treat the transaction as completed once we also have keys to download - const completed = _completed && !keyfileGenerating; - - return { - completed, - filename, - keyfileBind, - ...rest, - }; -} +import { useSetNetworkKeys } from 'lib/usetSetNetworkKeys'; export default function UrbitOSNetworkKeys({ manualNetworkSeed, @@ -158,7 +43,6 @@ export default function UrbitOSNetworkKeys({ getPendingTransactions, checkForUpdates, } = useRoller(); - const [breach, setBreach] = useState(false); const [useCustomSeed, setUseCustomSeed] = useState(false); const [l2Completed, setL2Completed] = useState(false); @@ -172,7 +56,7 @@ export default function UrbitOSNetworkKeys({ bind, keyfileBind, txHashes, - } = useSetKeys(manualNetworkSeed, setManualNetworkSeed); + } = useSetNetworkKeys(manualNetworkSeed, setManualNetworkSeed); useEffect(() => { if (completed) { @@ -215,7 +99,7 @@ export default function UrbitOSNetworkKeys({ ({ valid, values, form }) => { if (valid) { const networkSeed = useCustomSeed ? values.networkSeed : undefined; - construct(networkSeed, breach); + construct(networkSeed, false); setManualNetworkSeed(networkSeed); } else { unconstruct(); @@ -223,25 +107,17 @@ export default function UrbitOSNetworkKeys({ if (!useCustomSeed && values.networkSeed) { form.change('networkSeed', ''); - setBreach(false); setManualNetworkSeed(''); } }, - [ - construct, - unconstruct, - breach, - setManualNetworkSeed, - setBreach, - useCustomSeed, - ] + [construct, unconstruct, setManualNetworkSeed, useCustomSeed] ); const setNetworkKeys = useCallback(async () => { setLoading(true); try { await configureNetworkKeys({ - breach, + breach: false, customNetworkSeed: manualNetworkSeed, }); // TODO: just use the tx hash instead? @@ -265,7 +141,6 @@ export default function UrbitOSNetworkKeys({ setLoading(false); } }, [ - breach, manualNetworkSeed, getPendingTransactions, point, @@ -282,13 +157,6 @@ export default function UrbitOSNetworkKeys({ ? 'Set Network Keys' : 'Initiate Network Keys'; - const bootMessage = () => ( - - Download your new Network Key to boot your ship. Be sure to delete your - old pier before booting with this new key. - - ); - const resetMessage = () => ( <> @@ -309,14 +177,6 @@ export default function UrbitOSNetworkKeys({ ); - const waitMessage = () => ( - - Important: you will need to wait until the next L2 batch - is submitted to the roller to boot your ship. If you do not wait, then you - will not be able to boot your ship. - - ); - const buttonText = `${hasKeys ? 'Reset' : 'Set'} Network Keys`; const isComplete = completed || l2Completed; @@ -331,8 +191,7 @@ export default function UrbitOSNetworkKeys({ - {breach ? bootMessage() : resetMessage()} - {breach && point.isL2 ? waitMessage() : null} + {resetMessage()} {point.isL2 ? ( {!isComplete && ( - setBreach(!breach)}> - - Factory Reset - - - Use if your ship is corrupted, you lost your files, or -
- you want to erase your data -
setUseCustomSeed(!useCustomSeed)}> @@ -387,10 +231,10 @@ export default function UrbitOSNetworkKeys({ selected={useCustomSeed} disabled={inputsLocked} /> - Custom Network Seed + Use Custom Network Seed (optional) - Enter your own custom network seed to derive from + Enter your own network seed from which to derive the keys {useCustomSeed && ( <> diff --git a/src/worker/index.ts b/src/worker/index.ts index 843af214a..02b61c8b4 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -1,6 +1,7 @@ import { Buffer } from 'buffer/'; +// eslint-disable-next-line (self as any).Buffer = Buffer; export const walletgenWorker = new ComlinkWorker( new URL('./worker.ts', import.meta.url) -) \ No newline at end of file +);