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

Add use two otp options on create wallet #65

Merged
merged 30 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
658f052
Add use two otp options on create wallet
haolinj Aug 3, 2021
0d7ce6b
Use same name for second otp with suffix and adjust the instruction text
haolinj Aug 4, 2021
0f497a4
Preset second otp qr data and not overriding otp name in the state
haolinj Aug 4, 2021
bef6494
Merge branch 'master' of github.com:polymorpher/one-wallet into clien…
haolinj Aug 4, 2021
7a845ae
Add otp2 for transfer and potentially restore wallet
haolinj Aug 4, 2021
a9adb9d
Merge branch 'client-security' of github.com:polymorpher/one-wallet i…
haolinj Aug 4, 2021
e1bff30
Auto focus otp2 input box when otp1 input box is filled
haolinj Aug 4, 2021
f606270
fix issues with wasm in webpack compilation
polymorpher Aug 5, 2021
73deab0
improve Create screen texts, positioning, and user experience
polymorpher Aug 5, 2021
a5b9f93
remove redundant new lines
polymorpher Aug 5, 2021
41c3f4a
Merge branch 'client-security' into client-security-frontend
polymorpher Aug 5, 2021
266c747
Merge remote-tracking branch 'origin/client-security' into client-sec…
polymorpher Aug 5, 2021
57ef96b
minor
polymorpher Aug 5, 2021
f4346b8
clean up
polymorpher Aug 5, 2021
ace8dd7
ditto
polymorpher Aug 5, 2021
ca1f770
ONE Wallet -> 1wallet
polymorpher Aug 5, 2021
dc64057
otp entry name change
polymorpher Aug 5, 2021
b6ebe9b
text update
polymorpher Aug 5, 2021
26a0b54
better syntax for computeRecoveryHash
polymorpher Aug 5, 2021
e213814
v8 contract
polymorpher Aug 5, 2021
ec5ab14
revealRecovery with data
polymorpher Aug 5, 2021
36a3594
doRecovery
polymorpher Aug 5, 2021
8c3478e
skip checking minor version
polymorpher Aug 5, 2021
4d658c5
flow: allow index override
polymorpher Aug 5, 2021
344f9bd
relayer: show reveal params on verbose mode
polymorpher Aug 5, 2021
5970e3e
doRecovery: fix issues
polymorpher Aug 5, 2021
7c13508
rearrange ui elements
polymorpher Aug 5, 2021
45af94f
make restore work
polymorpher Aug 5, 2021
08834d7
Add 2 otp for recover address setup
haolinj Aug 5, 2021
e11ec8d
Merge branch 'client-security-frontend' of github.com:polymorpher/one…
haolinj Aug 5, 2021
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
44 changes: 22 additions & 22 deletions code/build/contracts/ONEWallet.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions code/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"babel-loader": "^8.2.2",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"babel-preset-react": "^6.24.1",
"base64-loader": "^1.0.0",
"copy-webpack-plugin": "^5.0.5",
"core-js": "^3.13.0",
"css-loader": "^5.2.6",
Expand Down
132 changes: 102 additions & 30 deletions code/client/src/pages/Create.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,21 @@ import api from '../api'
import ONEUtil from '../../../lib/util'
import ONENames from '../../../lib/names'
// import { uniqueNamesGenerator, colors, animals } from 'unique-names-generator'
import { Button, Row, Space, Typography, Slider, Image, message, Progress, Timeline, Select } from 'antd'
import { RedoOutlined, LoadingOutlined, SearchOutlined } from '@ant-design/icons'
import {
Button,
Row,
Space,
Typography,
Slider,
Image,
message,
Progress,
Timeline,
Select,
Checkbox,
Tooltip
} from 'antd'
import { RedoOutlined, LoadingOutlined, SearchOutlined, QuestionCircleOutlined } from '@ant-design/icons'
import humanizeDuration from 'humanize-duration'
import AnimatedSection from '../components/AnimatedSection'
import b32 from 'hi-base32'
Expand Down Expand Up @@ -37,16 +50,32 @@ const genName = (existingNames) => {
return name
}

const generateOtpSeed = () => {
const otpSeedBuffer = new Uint8Array(20)
return window.crypto.getRandomValues(otpSeedBuffer)
}

const sectionViews = {
setupWalletDetails: 1,
setupOtp: 2,
setupSecondOtp: 3,
prepareWallet: 4,
walletSetupDone: 5
}

const Create = () => {
const generateNewOtpName = () => genName(Object.keys(wallets).map(k => wallets[k].name))

const { isMobile } = useWindowDimensions()
const dispatch = useDispatch()
const history = useHistory()
const network = useSelector(state => state.wallet.network)
const wallets = useSelector(state => state.wallet.wallets)
const [name, setName] = useState(genName(Object.keys(wallets).map(k => wallets[k].name)))
const otpSeedBuffer = new Uint8Array(20)
const [name, setName] = useState(generateNewOtpName())
// eslint-disable-next-line no-unused-vars
const [seed, setSeed] = useState(generateOtpSeed())
// eslint-disable-next-line no-unused-vars
const [seed, setSeed] = useState(window.crypto.getRandomValues(otpSeedBuffer))
const [seed2, setSeed2] = useState(generateOtpSeed())
const [duration, setDuration] = useState(WalletConstants.defaultDuration)
const [lastResortAddress, setLastResortAddress] = useState()
const [dailyLimit] = useState(WalletConstants.defaultDailyLimit)
Expand All @@ -60,52 +89,62 @@ const Create = () => {
const [progressStage, setProgressStage] = useState(0)
const [address, setAddress] = useState() // '0x12345678901234567890'
const [effectiveTime, setEffectiveTime] = useState()
const [doubleOtp, setDoubleOtp] = useState(false)

const [durationVisible, setDurationVisible] = useState(false)
const [section, setSection] = useState(2)
const [section, setSection] = useState(sectionViews.setupOtp)
const [qrCodeData, setQRCodeData] = useState()
const [secondOtpQrCodeData, setSecondOtpQrCodeData] = useState()
const [otp, setOtp] = useState('')

const [deploying, setDeploying] = useState()

const otpRef = useRef()

const getQRCodeUri = () => {
const getQRCodeUri = (otpSeed, otpDisplayName) => {
// otpauth://TYPE/LABEL?PARAMETERS
return `otpauth://totp/${name}?secret=${b32.encode(seed)}&issuer=Harmony`
return `otpauth://totp/${otpDisplayName}?secret=${b32.encode(otpSeed)}&issuer=Harmony`
}

useEffect(() => {
(async function () {
const uri = getQRCodeUri()
const data = await qrcode.toDataURL(uri, { errorCorrectionLevel: 'low', width: isMobile ? 192 : 256 })
setQRCodeData(data)
const otpUri = getQRCodeUri(seed, name)
const secondOtpUri = getQRCodeUri(seed2, `${name} - 2nd`)
const otpQrCodeData = await qrcode.toDataURL(otpUri, { errorCorrectionLevel: 'low', width: isMobile ? 192 : 256 })
const secondOtpQrCodeData = await qrcode.toDataURL(secondOtpUri, { errorCorrectionLevel: 'low', width: isMobile ? 192 : 256 })
setQRCodeData(otpQrCodeData)
setSecondOtpQrCodeData(secondOtpQrCodeData)
})()
}, [name])

useEffect(() => {
if (section === 2 && worker) {
if (section === sectionViews.setupOtp && worker) {
console.log('posting to worker')
const t = Math.floor(Date.now() / WalletConstants.interval) * WalletConstants.interval
setEffectiveTime(t)
worker && worker.postMessage({
seed, effectiveTime: t, duration, slotSize, interval: WalletConstants.interval
seed, seed2, effectiveTime: t, duration, slotSize, interval: WalletConstants.interval
})
}
}, [section, worker])

useEffect(() => {
const settingUpSecondOtp = section === sectionViews.setupSecondOtp
if (otp.length !== 6) {
return
}
const expected = ONEUtil.genOTP({ seed })
const currentSeed = settingUpSecondOtp ? seed2 : seed
const expected = ONEUtil.genOTP({ seed: currentSeed })
const code = new DataView(expected.buffer).getUint32(0, false).toString()
setOtp('')
if (code.padStart(6, '0') !== otp.padStart(6, '0')) {
console.log(`Expected: ${code}. Got: ${otp}`)
message.error('Code is incorrect. Please try again.')
setOtp('')
otpRef?.current?.focusInput(0)
} else if (doubleOtp && !settingUpSecondOtp) {
setSection(sectionViews.setupSecondOtp)
otpRef?.current?.focusInput(0)
} else {
setSection(3)
setSection(sectionViews.prepareWallet)
}
}, [otp])

Expand All @@ -122,7 +161,6 @@ const Create = () => {
message.error('Cannot deploy wallet. Error: root is not set.')
return
}

let normalizedAddress = ''
if (lastResortAddress !== '') {
// Ensure valid address for both 0x and one1 formats
Expand Down Expand Up @@ -155,6 +193,7 @@ const Create = () => {
dailyLimit: ONEUtil.toFraction(dailyLimit).toString(),
hseed: ONEUtil.hexView(hseed),
network,
doubleOtp,
}
await storeLayers()
dispatch(walletActions.updateWallet(wallet))
Expand Down Expand Up @@ -193,7 +232,7 @@ const Create = () => {

return (
<>
<AnimatedSection show={section === 1} style={{ maxWidth: 640 }}>
<AnimatedSection show={section === sectionViews.setupWalletDetails} style={{ maxWidth: 640 }}>
<Heading>What do you want to call your wallet?</Heading>
<Hint>This is only stored on your computer to distinguish your wallets.</Hint>
<Row align='middle' style={{ marginBottom: 32, marginTop: 16 }}>
Expand All @@ -203,12 +242,11 @@ const Create = () => {
value={name} onChange={({ target: { value } }) => setName(value)}
style={{ padding: 0 }}
/>
<Button type='primary' shape='round' size='large' onClick={() => setSection(2)}>Next</Button>
<Button type='primary' shape='round' size='large' onClick={() => setSection(sectionViews.setupOtp)}>Next</Button>
</Space>
</Row>

<Space direction='vertical'>
<Hint>Next, we will set up a ONE Wallet that expires in a year. When the wallet expires, you may create a new wallet and transfer the funds. The funds can also be recovered to an address you set later.</Hint>
<Hint>Next, we will set up a 1wallet that expires in a year. When the wallet expires, you may create a new wallet and transfer the funds. The funds can also be recovered to an address you set later.</Hint>
<Link onClick={() => setDurationVisible(true)}>Need more time?</Link>
{durationVisible &&
<Space>
Expand All @@ -221,11 +259,11 @@ const Create = () => {
</Space>}
</Space>
</AnimatedSection>
<AnimatedSection show={section === 2} style={{ maxWidth: 640 }}>
<AnimatedSection show={section === sectionViews.setupOtp} style={{ maxWidth: 640 }}>
<Row>
<Space direction='vertical'>
{/* <Heading>Now, scan the QR code with your Google Authenticator</Heading> */}
<Heading>Create Your ONE Wallet</Heading>
<Heading>Create Your 1wallet</Heading>
<Hint>You need the 6-digit code from Google authenticator to transfer funds. You can restore your wallet using Google authenticator on any device.</Hint>
<Row justify='center'>
{qrCodeData && <Image src={qrCodeData} preview={false} width={isMobile ? 192 : 256} />}
Expand All @@ -234,7 +272,41 @@ const Create = () => {
</Row>
<Row>
<Space direction='vertical' size='large' align='center'>
<Hint>After you are done, type in the 6-digit code from Google authenticator.</Hint>
<Hint>After you are done, type in the 6-digit code from Google authenticator</Hint>
<Hint>Code for <b>Harmony ({name})</b></Hint>
<OtpBox
shouldAutoFocus
ref={otpRef}
value={otp}
onChange={setOtp}
/>
<Checkbox onChange={() => setDoubleOtp(!doubleOtp)}>
<Space>
<Hint>
Use two codes to enhance security
</Hint>
<Tooltip title={<div>You will need to scan another QR-code on the next page. Each time you make a transaction, you will need to type in two 6-digit codes, which are shown simultaneously next to each other on your Google authenticator.<br /><br />This is advisable if you intend to make larger transactions with this wallet</div>}>
<QuestionCircleOutlined />
</Tooltip>
</Space>
</Checkbox>
</Space>
</Row>
</AnimatedSection>
<AnimatedSection show={section === sectionViews.setupSecondOtp} style={{ maxWidth: 640 }}>
<Row>
<Space direction='vertical'>
<Heading>Create Your 1wallet (second code)</Heading>
<Hint align='center'>Scan with your Google Authenticator to setup the <b>second</b> code</Hint>
<Row justify='center'>
{secondOtpQrCodeData && <Image src={secondOtpQrCodeData} preview={false} width={isMobile ? 192 : 256} />}
</Row>
</Space>
</Row>
<Row>
<Space direction='vertical' size='large' align='center'>
<Hint>Type in the <b>second</b> 6-digit code from Google authenticator</Hint>
<Hint>Code for <b>Harmony ({name} - 2nd)</b></Hint>
<OtpBox
shouldAutoFocus
ref={otpRef}
Expand All @@ -244,10 +316,10 @@ const Create = () => {
</Space>
</Row>
</AnimatedSection>
<AnimatedSection show={section === 3} style={{ maxWidth: 640 }}>
<AnimatedSection show={section === sectionViews.prepareWallet} style={{ maxWidth: 640 }}>
<Row>
<Space direction='vertical'>
<Heading>Prepare Your ONE Wallet</Heading>
<Heading>Prepare Your 1wallet</Heading>
</Space>
</Row>
{/* <Row style={{ marginBottom: 16 }}> */}
Expand Down Expand Up @@ -300,7 +372,7 @@ const Create = () => {
percent={progress}
/>
<Space direction='vertical'>
<Timeline pending={progressStage < 2 && 'Securing your keyless ONE Wallet'}>
<Timeline pending={progressStage < 2 && 'Securing your keyless 1wallet'}>
<Timeline.Item color={progressStage < 1 ? 'grey' : 'green'}>Securing the wallet</Timeline.Item>
<Timeline.Item color={progressStage < 2 ? 'grey' : 'green'}>Preparing signatures</Timeline.Item>
</Timeline>
Expand All @@ -312,12 +384,12 @@ const Create = () => {
<Row>
<Space direction='vertical'>
<Hint>No private key. No mnemonic. Simple and Secure. </Hint>
<Hint>To learn more, visit <Link href='https://github.com/polymorpher/one-wallet/wiki'>ONE Wallet Wiki</Link></Hint>
<Hint>To learn more, visit <Link href='https://github.com/polymorpher/one-wallet/wiki'>1wallet Wiki</Link></Hint>
<Hint>In Beta, your wallet is subject to a daily spending limit of {WalletConstants.defaultDailyLimit} ONE</Hint>
</Space>
</Row>
</AnimatedSection>
<AnimatedSection show={section === 4}>
<AnimatedSection show={section === sectionViews.walletSetupDone}>
<Space direction='vertical'>
<Heading>You are all set!</Heading>
<Space direction='vertical' size='small'>
Expand Down
21 changes: 16 additions & 5 deletions code/client/src/pages/Restore.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const Restore = () => {
const dispatch = useDispatch()
const [videoDevices, setVideoDevices] = useState([])
const [secret, setSecret] = useState()
const [secret2, setSecret2] = useState()
const [name, setName] = useState()
const [device, setDevice] = useState()
const { isMobile } = useWindowDimensions()
Expand Down Expand Up @@ -63,10 +64,19 @@ const Restore = () => {
const data = new URL(e).searchParams.get('data')
const params = MigrationPayload.decode(Buffer.from(data, 'base64')).otpParameters
const filteredParams = params.filter(e => e.issuer === 'ONE Wallet' || e.issuer === 'Harmony')
if (filteredParams.length > 1) {
message.error('You selected more than 1 ONE Wallet code to export. Please reselect on Google Authenticator')
if (filteredParams.length > 2) {
message.error('You selected more than one authenticator entry to export. Please reselect on Google Authenticator')
return
}
if (filteredParams.length === 2) {
const names = filteredParams.map(e => e.name.split('-')[0].trim()).map(e => e.split('(')[0].trim())
if (names[0] !== names[1]) {
message.error('You selected two wallets with different names. If you want to select two entries belonging to the same wallet, make sure they have the same name and the second one has "- 2nd" in the end')
return
}
const { secret } = filteredParams[1]
setSecret2(secret)
}
const { secret, name } = filteredParams[0]
setSecret(secret)
setName(name)
Expand Down Expand Up @@ -107,7 +117,7 @@ const Restore = () => {
setProgressStage(stage)
}
if (status === 'done') {
const { hseed, root: computedRoot, layers } = result
const { hseed, root: computedRoot, layers, doubleOtp } = result
if (!ONEUtil.bytesEqual(ONEUtil.hexToBytes(root), computedRoot)) {
console.error('Roots are not equal', root, ONEUtil.hexString(computedRoot))
message.error('Verification failed. Your authenticator QR code might correspond to a different contract address.')
Expand All @@ -123,6 +133,7 @@ const Restore = () => {
lastResortAddress,
dailyLimit,
hseed: ONEUtil.hexView(hseed),
doubleOtp,
network
}
dispatch(walletActions.updateWallet(wallet))
Expand All @@ -134,7 +145,7 @@ const Restore = () => {
}
console.log('[Restore] Posting to worker')
worker && worker.postMessage({
seed: secret, effectiveTime, duration, slotSize, interval: WalletConstants.interval
seed: secret, seed2: secret2, effectiveTime, duration, slotSize, interval: WalletConstants.interval
})
} catch (ex) {
Sentry.captureException(ex)
Expand Down Expand Up @@ -249,7 +260,7 @@ const Restore = () => {
percent={progress}
/>
<Space direction='vertical'>
<Timeline pending={progressStage < 2 && 'Rebuilding your ONE Wallet'}>
<Timeline pending={progressStage < 2 && 'Rebuilding your 1wallet'}>
<Timeline.Item color={progressStage < 1 ? 'grey' : 'green'}>Recomputing proofs for each time interval</Timeline.Item>
<Timeline.Item color={progressStage < 2 ? 'grey' : 'green'}>Preparing hashes for verification</Timeline.Item>
</Timeline>
Expand Down
Loading