diff --git a/.prettierrc b/.prettierrc
index 548c7c2..cfab77c 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -3,4 +3,4 @@
"singleQuote": true,
"tabWidth": 2,
"endOfLine": "auto"
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index a050b1a..a6c9b4c 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,9 @@
# Zero-Knowledge 2-Factor Authentication 🗝️ (zk-2FA)
-
The goal of this project is to provide 2FA for EVM compatible blockchains.
-We follow a parallel approach for a twofold Authentication solution. The first implements the popular and broadly adopted TOTP 2FA with a trusted validator. The second solution implements a password-generator based zk proof, which is validated onChain providing a zero-trust security level.
+We follow a parallel approach for a twofold Authentication solution. The first implements the popular and broadly adopted TOTP 2FA with a trusted validator. The second solution implements a password-generator based zk proof, which is validated onChain providing a zero-trust security level.
-Further we provide a dapp to facilitate user-interaction with our smrt-contracts. All dapp interactions can likewise be performed manually per console.
+Further we provide a dapp to facilitate user-interaction with our smrt-contracts. All dapp interactions can likewise be performed manually per console.
## TOTP 2FA
@@ -15,7 +14,6 @@ A picturesque flow-chart of our TOTP 2FA solution:
**Artworq in the making**
-
## Contribute
Feedback and contributions are always welcome 🤗
diff --git a/backend/package.json b/backend/package.json
index 29f257b..b6ebfbd 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,7 +1,7 @@
{
"name": "hardhat-project",
"scripts": {
- "prettier": "prettier --write . --config ../.prettierrc"
+ "prettier": "prettier --write ../ --config ../.prettierrc"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^1.0.3",
diff --git a/dapp/components/CardChoice.tsx b/dapp/components/CardChoice.tsx
new file mode 100644
index 0000000..7cfad36
--- /dev/null
+++ b/dapp/components/CardChoice.tsx
@@ -0,0 +1,69 @@
+import { useTheme } from 'next-themes'
+import Image from 'next/image'
+
+interface CardChoiceProps {
+ authType: string
+ setAuthType: (arg: string) => void
+}
+
+const CardChoice = (props: CardChoiceProps) => {
+ const { theme } = useTheme()
+ return (
+ {
+ totp: (
+
+ ),
+ zk: (
+
+ ),
+ }[props.authType] || <>>
+ )
+}
+
+export default CardChoice
diff --git a/dapp/components/DropdownAccount.tsx b/dapp/components/DropdownAccount.tsx
new file mode 100644
index 0000000..a2e8068
--- /dev/null
+++ b/dapp/components/DropdownAccount.tsx
@@ -0,0 +1,96 @@
+/* This example requires Tailwind CSS v2.0+ */
+import { Fragment } from 'react'
+import { Menu, Transition } from '@headlessui/react'
+import {
+ ArrowTopRightOnSquareIcon,
+ ArrowRightOnRectangleIcon,
+} from '@heroicons/react/20/solid'
+import { shortenAddress, useEthers, useLookupAddress } from '@usedapp/core'
+
+function classNames(...classes: any) {
+ return classes.filter(Boolean).join(' ')
+}
+
+interface DropdownProps {
+ account: string
+}
+
+const DropdownAccount = (props: DropdownProps) => {
+ const { deactivate } = useEthers()
+ const { ens } = useLookupAddress(props.account)
+
+ return (
+
+ )
+}
+
+export default DropdownAccount
diff --git a/dapp/components/Layout.tsx b/dapp/components/Layout.tsx
new file mode 100644
index 0000000..b65a4c5
--- /dev/null
+++ b/dapp/components/Layout.tsx
@@ -0,0 +1,16 @@
+import { Navbar } from './'
+import Head from 'next/head'
+
+export default function Layout(props: any) {
+ return (
+ <>
+
+ zkAuth
+
+
+
+
+ {props.children}
+ >
+ )
+}
diff --git a/dapp/components/modalVerify.tsx b/dapp/components/ModalVerifyTotp.tsx
similarity index 98%
rename from dapp/components/modalVerify.tsx
rename to dapp/components/ModalVerifyTotp.tsx
index 3349b9e..494d88b 100644
--- a/dapp/components/modalVerify.tsx
+++ b/dapp/components/ModalVerifyTotp.tsx
@@ -6,12 +6,12 @@ import {
CheckIcon,
} from '@heroicons/react/24/outline'
-interface ModalVerifyProps {
+interface ModalVerifyTotpProps {
verified: boolean
verifyCode: any
}
-const ModalVerify = (props: ModalVerifyProps) => {
+const ModalVerifyTotp = (props: ModalVerifyTotpProps) => {
const [open, setOpen] = useState(false)
const onSubmit = (e: React.UIEvent) => {
e.preventDefault()
@@ -135,4 +135,4 @@ const ModalVerify = (props: ModalVerifyProps) => {
)
}
-export default ModalVerify
+export default ModalVerifyTotp
diff --git a/dapp/components/TotpSetup.tsx b/dapp/components/TotpSetup.tsx
new file mode 100644
index 0000000..8d42882
--- /dev/null
+++ b/dapp/components/TotpSetup.tsx
@@ -0,0 +1,123 @@
+import { ArrowUturnLeftIcon } from '@heroicons/react/24/outline'
+import { useEthers } from '@usedapp/core'
+import { useState, useRef, useEffect } from 'react'
+import { usePinInput, PinInputActions } from 'react-pin-input-hook'
+import { ModalVerifyTotp, QrCodeAuth } from './'
+
+var jsotp = require('jsotp')
+var base32 = require('thirty-two')
+
+interface TotpSetupProps {
+ setAuthType: (arg: string) => void
+}
+
+const TotpSetup = (props: TotpSetupProps) => {
+ const { account, library: provider } = useEthers()
+
+ // Secret State Management
+ const [secret, setSecret] = useState('')
+ const [blur, setBlur] = useState('opacity-50 blur-sm')
+ const loadSecret = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (provider != undefined) {
+ const signer = provider.getSigner()
+ const signature = await signer.signMessage('zkAuth') // TODO: Random Input? ZK?
+ const secretEncoded = base32
+ .encode(signature)
+ .toString()
+ .replace(/=/g, '')
+ setSecret(secretEncoded)
+ setBlur('')
+ }
+ }
+
+ // PIN state management
+ const [verified, setVerified] = useState(false)
+ const [pin, setPin] = useState(['', '', '', '', '', ''])
+ const [error, setError] = useState(false)
+ const actionRef = useRef(null)
+ const { fields } = usePinInput({
+ values: pin,
+ onChange: setPin,
+ error,
+ actionRef,
+ placeholder: '•',
+ })
+
+ function verifyCode(e: React.FormEvent) {
+ e.preventDefault()
+ // Check if there is at least one empty field. If there is, the input is considered empty.
+ if (pin.includes('')) {
+ // Setting the error.
+ setError(true)
+ // We set the focus on the first empty field if `error: true` was passed as a parameter in `options`.
+ actionRef.current?.focus()
+ }
+ const verifier = jsotp.TOTP(secret)
+ if (verifier.verify(pin.join(''))) {
+ setVerified(true)
+ } else {
+ setVerified(false)
+ }
+ }
+
+ if (!account) return null
+ return (
+
+
+
+
+
+
+ )
+}
+
+export default TotpSetup
diff --git a/dapp/components/ZkPasswordSetup.tsx b/dapp/components/ZkPasswordSetup.tsx
new file mode 100644
index 0000000..7eaa718
--- /dev/null
+++ b/dapp/components/ZkPasswordSetup.tsx
@@ -0,0 +1,112 @@
+import {
+ ArrowUturnLeftIcon,
+ CheckIcon,
+ XMarkIcon,
+} from '@heroicons/react/24/outline'
+import { useEthers } from '@usedapp/core'
+import { useState } from 'react'
+
+import dynamic from 'next/dynamic'
+const PasswordChecklist = dynamic(import('react-password-checklist'), {
+ ssr: false,
+})
+interface ZkSetupProps {
+ setAuthType: (arg: string) => void
+}
+const ZkPasswordSetup = (props: ZkSetupProps) => {
+ const { account, library: provider } = useEthers()
+
+ // PIN state management
+ const [password, setPassword] = useState('')
+ const [passwordAgain, setPasswordAgain] = useState('')
+ const [isValid, setIsValid] = useState(false)
+
+ // Set password to blockchain
+ const onSubmit = (e: React.FormEvent) => {
+ e.preventDefault()
+ }
+
+ if (!account) return null
+ return (
+
+
+
+
+
+
+ )
+}
+
+export default ZkPasswordSetup
diff --git a/dapp/components/connectWalletButton.tsx b/dapp/components/connectWalletButton.tsx
index 810529d..11d1889 100644
--- a/dapp/components/connectWalletButton.tsx
+++ b/dapp/components/connectWalletButton.tsx
@@ -3,6 +3,7 @@ import { useEthers } from '@usedapp/core'
import Web3Modal from 'web3modal'
import WalletConnectProvider from '@walletconnect/web3-provider'
import { useTheme } from 'next-themes'
+import { ethers } from 'ethers'
const ConnectWalletButton = () => {
const { theme } = useTheme()
@@ -28,6 +29,7 @@ const ConnectWalletButton = () => {
const [loaded, setLoaded] = useState(false)
useEffect(() => setLoaded(true), [])
+ // Set up Web3modal
const [web3Modal, setWeb3Modal] = useState(undefined)
useEffect(() => {
const providerOptions = {
@@ -47,22 +49,16 @@ const ConnectWalletButton = () => {
},
}
- const newWeb3Modal = new Web3Modal({
- providerOptions,
- cacheProvider: false,
- })
- setWeb3Modal(newWeb3Modal)
- }, [])
+ if (loaded) {
+ const newWeb3Modal = new Web3Modal({
+ providerOptions,
+ cacheProvider: false,
+ theme: theme,
+ })
- // const connect = async () => {
- // try {
- // const provider = await web3Modal?.connect()
- // await activate(provider)
- // setActivateError("")
- // } catch (error: any) {
- // setActivateError(error.message)
- // }
- // }
+ setWeb3Modal(newWeb3Modal)
+ }
+ }, [loaded, theme])
const connect = useCallback(async () => {
try {
@@ -74,12 +70,22 @@ const ConnectWalletButton = () => {
}
}, [web3Modal, activate])
- // useEffect(() => {
- // if (web3Modal && web3Modal.cachedProvider) {
- // console.log(web3Modal.cachedProvider)
- // connect()
- // }
- // }, [connect, web3Modal])
+ // Set up provider if already connected
+ useEffect(() => {
+ const { ethereum } = window
+ const checkMetaMaskConnected = async () => {
+ var provider = new ethers.providers.Web3Provider(ethereum)
+ const accounts = await provider.listAccounts()
+ const connected = accounts.length > 0
+ console.log('CONNECTED', connected)
+ if (connected) {
+ activate(provider)
+ }
+ }
+ if (ethereum) {
+ checkMetaMaskConnected()
+ }
+ })
if (!loaded) return null
diff --git a/dapp/components/index.ts b/dapp/components/index.ts
new file mode 100644
index 0000000..c99178f
--- /dev/null
+++ b/dapp/components/index.ts
@@ -0,0 +1,11 @@
+export { default as ConnectWalletButton } from './ConnectWalletButton'
+export { default as DropdownAccount } from './DropdownAccount'
+export { default as LogInBox } from './LogInBox'
+export { default as ModalVerifyTotp } from './ModalVerifyTotp'
+export { default as Navbar } from './Navbar'
+export { default as QrCodeAuth } from './QrCodeAuth'
+export { default as ToggleColorMode } from './ToggleColorMode'
+export { default as TotpSetup } from './TotpSetup'
+export { default as ZkPasswordSetup } from './ZkPasswordSetup'
+export { default as CardChoice } from './CardChoice'
+export { default as Layout } from './Layout'
diff --git a/dapp/components/logInBox.tsx b/dapp/components/logInBox.tsx
index ea8ca78..855f803 100644
--- a/dapp/components/logInBox.tsx
+++ b/dapp/components/logInBox.tsx
@@ -1,123 +1,72 @@
-import { useEthers, shortenAddress, useLookupAddress } from '@usedapp/core'
-import { ethers } from 'ethers'
-import { useState, useRef, useEffect } from 'react'
-import { usePinInput, PinInputActions } from 'react-pin-input-hook'
-import ConnectWalletButton from './connectWalletButton'
-import ModalVerify from './modalVerify'
-import QrCodeAuth from './qrCodeAuth'
+import { useEthers } from '@usedapp/core'
+import { useState } from 'react'
+import { motion } from 'framer-motion'
-var jsotp = require('jsotp')
-var base32 = require('thirty-two')
+import { ConnectWalletButton, TotpSetup, ZkPasswordSetup, CardChoice } from './'
const LogInBox = () => {
const { account, library: provider } = useEthers()
- const { ens } = useLookupAddress(account)
-
- // Secret State Management
- const [secret, setSecret] = useState('')
- const [blur, setBlur] = useState('opacity-50 blur-sm')
- const loadSecret = async (e: React.FormEvent) => {
- e.preventDefault()
- if (provider != undefined) {
- const signer = provider.getSigner()
- const signature = await signer.signMessage('zkAuth') // TODO: Random Input? ZK?
- const secretEncoded = base32
- .encode(signature)
- .toString()
- .replace(/=/g, '')
- setSecret(secretEncoded)
- setBlur('')
- }
- }
-
- // PIN state management
- const [verified, setVerified] = useState(false)
- const [pin, setPin] = useState(['', '', '', '', '', ''])
- const [error, setError] = useState(false)
- const actionRef = useRef(null)
- const { fields } = usePinInput({
- values: pin,
- onChange: setPin,
- error,
- actionRef,
- placeholder: '•',
- })
-
- function verifyCode(e: React.FormEvent) {
- e.preventDefault()
- // Check if there is at least one empty field. If there is, the input is considered empty.
- if (pin.includes('')) {
- // Setting the error.
- setError(true)
- // We set the focus on the first empty field if `error: true` was passed as a parameter in `options`.
- actionRef.current?.focus()
- }
- const verifier = jsotp.TOTP(secret)
- if (verifier.verify(pin.join(''))) {
- setVerified(true)
- } else {
- setVerified(false)
- }
- }
+ const [authType, setAuthType] = useState('')
return (
<>
{!account ? (
-
+
-
+
) : (
-
+
+
+ ),
+ zk: (
+
+
+
+ ),
+ }[authType] || (
+
+
+
+
+
+
+
+
+ )
)}
>
)
diff --git a/dapp/components/navbar.tsx b/dapp/components/navbar.tsx
index 8c1d1bc..d39f844 100644
--- a/dapp/components/navbar.tsx
+++ b/dapp/components/navbar.tsx
@@ -1,18 +1,20 @@
-import ToggleColorMode from './toggleColorMode'
+import { useEthers } from '@usedapp/core'
+import { DropdownAccount, ToggleColorMode } from './'
const Navbar = () => {
+ const { account } = useEthers()
+
return (
-