add login -> backup -> connect pages

This commit is contained in:
Joel Gustafson
2021-11-19 10:53:54 -05:00
parent fc460a9a1b
commit 0c521e4faa
7 changed files with 1431 additions and 106 deletions

1218
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,8 +20,11 @@
"homepage": "https://github.com/joeltg/zk-group-sigs-server#readme",
"dependencies": {
"@headlessui/react": "^1.4.2",
"@ethersproject/providers": "^5.5.0",
"@metamask/detect-provider": "^1.2.0",
"@prisma/client": "^3.5.0",
"buffer": "^6.0.3",
"ethers": "^5.5.1",
"next": "^12.0.4",
"prisma": "^3.5.0",
"react": "^17.0.2",

38
pages/backup.tsx Normal file
View File

@@ -0,0 +1,38 @@
import React, { useEffect, useState } from "react"
import { LOCAL_STORAGE_SECRET_KEY } from "utils/localStorage"
import { mimcHash } from "utils/mimc"
import Link from "next/link"
import { useRouter } from "next/router"
export default function BackupPage(props: {}) {
const router = useRouter()
const [secret, setSecret] = useState<null | string>(null)
useEffect(() => {
const secret = localStorage.getItem(LOCAL_STORAGE_SECRET_KEY)
if (secret === null) {
router.push("/login")
} else {
const n = BigInt("0x" + secret)
const h = mimcHash(n)
setSecret(h.toString(16))
}
}, [])
return (
<div className="max-w-lg m-auto font-mono">
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
<div className="border border-gray-300 rounded-xl p-6">
<p>This is your ZK CHAT token.</p>
<p>Save it somewhere safe:</p>
<div className="break-all mt-6 mb-6">{secret}</div>
<Link href="/connect">
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2">
Next
</div>
</Link>
</div>
</div>
)
}

View File

@@ -4,7 +4,10 @@ export default function ConnectPage(props: {}) {
return (
<div className="max-w-lg m-auto font-mono">
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
<div className="border border-gray-300 rounded-xl p-6">cool</div>
<div className="border border-gray-300 rounded-xl p-6">
<div>Sign this message with your Ethereum wallet:</div>
<button>Sign in with Ethereum</button>
</div>
</div>
)
}

30
pages/hash.tsx Normal file
View File

@@ -0,0 +1,30 @@
import React, { useMemo, useState } from "react"
import { mimcHash } from "utils/mimc"
export default function MiMCPage(props: {}) {
const [value, setValue] = useState("")
const hash = useMemo(() => {
const bytes = Buffer.from(value)
const n = BigInt(bytes.length > 0 ? "0x" + bytes.toString("hex") : 0n)
return mimcHash(n)
}, [value])
return (
<div className="max-w-lg m-auto font-mono">
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
<div className="border border-gray-300 rounded-xl p-6">
<input
type="text"
value={value}
onChange={(event) => setValue(event.target.value)}
/>
<hr />
<div className="break-all">
<p>{hash.toString()}</p>
<hr />
<p>0x{hash.toString(16)}</p>
</div>
</div>
</div>
)
}

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react"
import React, { useEffect, useMemo, useState } from "react"
import { Buffer } from "buffer"
import type { GetServerSideProps } from "next"
import Link from "next/link"
@@ -7,6 +7,8 @@ import { Menu, Transition } from "@headlessui/react"
import { mimcHash } from "utils/mimc"
import { prove } from "utils/prove"
import { prisma } from "utils/prisma"
import { LOCAL_STORAGE_SECRET_KEY } from "utils/localStorage"
import { useRouter } from "next/router"
interface IndexPageProps {
userCount: number
@@ -20,14 +22,27 @@ export const getServerSideProps: GetServerSideProps<IndexPageProps, {}> =
}
}
function UserIcon({ address }) {
return <div className="inline-block h-6 w-6 bg-gray-200 rounded-full text-center text-gray-400 pt-0.5 ml-0.5">
{address}
</div>;
function UserIcon({ address }: { address: string }) {
return (
<div className="inline-block h-6 w-6 bg-gray-200 rounded-full text-center text-gray-400 pt-0.5 ml-0.5">
{address}
</div>
)
}
export default function Index(props: IndexPageProps) {
console.log("we have", props.userCount, "users")
const router = useRouter()
useEffect(() => {
const secret = localStorage.getItem(LOCAL_STORAGE_SECRET_KEY)
if (secret === null) {
// user doesn't have a secret key in localstorage
router.push("/login")
}
}, [])
const [secret, setSecret] = useState("")
const hash1 = useMemo(() => {
const hex = Buffer.from(secret).toString("hex")
@@ -42,27 +57,28 @@ export default function Index(props: IndexPageProps) {
const [message, setMessage] = useState("")
const messages = [
{
message: 'Hello world!',
proof: '',
group: ['0', '1', '2'],
reveals: [],
denials: [],
},
{
message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempo',
proof: '',
group: ['0', '1', '2'],
reveals: ['0'],
denials: ['1', '2'],
},
{
message: 'Random message',
proof: '',
group: ['0', '1', '2'],
reveals: [],
denials: [],
}
{
message: "Hello world!",
proof: "",
group: ["0", "1", "2"],
reveals: [],
denials: [],
},
{
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempo",
proof: "",
group: ["0", "1", "2"],
reveals: ["0"],
denials: ["1", "2"],
},
{
message: "Random message",
proof: "",
group: ["0", "1", "2"],
reveals: [],
denials: [],
},
]
return (
@@ -71,66 +87,68 @@ export default function Index(props: IndexPageProps) {
<h1 className="uppercase font-bold mt-16 mb-6 flex-1">zk chat</h1>
<div>
<Link href="/login">
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-14">Login</div>
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-14">
Login
</div>
</Link>
</div>
</div>
<div className="border border-gray-300 rounded-xl p-6">
<fieldset>
<legend>identify yourself</legend>
<label>
<span>secret pre-image</span>
<fieldset>
<legend>identify yourself</legend>
<label>
<span>secret pre-image</span>
<br />
<input
type="text"
value={secret}
onChange={(event) => setSecret(event.target.value)}
/>
</label>
<br />
<input
type="text"
value={secret}
onChange={(event) => setSecret(event.target.value)}
<label>
<span>hash1</span>
<br />
<code>{hash1}</code>
</label>
</fieldset>
<fieldset>
<legend>identify your group</legend>
<label>
<span>hash2</span>
<br />
<input
type="text"
value={hash2}
onChange={(event) => setHash2(event.target.value)}
/>
</label>
<br />
<label>
<span>hash3</span>
<br />
<input
type="text"
value={hash3}
onChange={(event) => setHash3(event.target.value)}
/>
</label>
</fieldset>
<fieldset>
<legend>message</legend>
<br />
<textarea
className="block w-full"
value={message}
onChange={(event) => setMessage(event.target.value)}
/>
</label>
<br />
<label>
<span>hash1</span>
<br />
<code>{hash1}</code>
</label>
</fieldset>
<fieldset>
<legend>identify your group</legend>
<label>
<span>hash2</span>
<br />
<input
type="text"
value={hash2}
onChange={(event) => setHash2(event.target.value)}
className="bg-pink text-white rounded-xl px-4 py-2 mt-6"
type="button"
onClick={() => prove({ secret, hash1, hash2, hash3, msg: message })}
value="Send your first message"
/>
</label>
<br />
<label>
<span>hash3</span>
<br />
<input
type="text"
value={hash3}
onChange={(event) => setHash3(event.target.value)}
/>
</label>
</fieldset>
<fieldset>
<legend>message</legend>
<br />
<textarea
className="block w-full"
value={message}
onChange={(event) => setMessage(event.target.value)}
/>
<input
className="bg-pink text-white rounded-xl px-4 py-2 mt-6"
type="button"
onClick={() => prove({ secret, hash1, hash2, hash3, msg: message })}
value="Send your first message"
/>
</fieldset>
</fieldset>
</div>
<div className="pt-6 pb-12">
<div className="mb-8 flex">
@@ -148,8 +166,11 @@ export default function Index(props: IndexPageProps) {
</div>
{messages.map((message, index) => (
<div key={index} className="bg-white rounded-2xl px-6 pt-5 pb-4 mb-4 leading-snug relative">
<div className="absolute top-3 right-5 text-right">
<div
key={index}
className="bg-white rounded-2xl px-6 pt-5 pb-4 mb-4 leading-snug relative"
>
<div className="absolute top-3 right-5 text-right">
<Menu>
<Menu.Button className="text-gray-300">&hellip;</Menu.Button>
<Transition
@@ -164,7 +185,9 @@ export default function Index(props: IndexPageProps) {
<Menu.Item>
{({ active }) => (
<a
className={`block ${active && 'bg-blue-500 text-white'}`}
className={`block ${
active && "bg-blue-500 text-white"
}`}
href="#"
onClick={(e) => e.preventDefault()}
>
@@ -175,7 +198,9 @@ export default function Index(props: IndexPageProps) {
<Menu.Item>
{({ active }) => (
<a
className={`block ${active && 'bg-blue-500 text-white'}`}
className={`block ${
active && "bg-blue-500 text-white"
}`}
href="#"
onClick={(e) => e.preventDefault()}
>
@@ -187,17 +212,22 @@ export default function Index(props: IndexPageProps) {
</Transition>
</Menu>
</div>
<div className="mb-5">
{message.message}
</div>
<div className="mb-5">{message.message}</div>
<div className="flex text-sm">
<div className="flex-1 text-gray-400">
{message.reveals.length > 0 ? 'From ' : 'From one of '}
{(message.reveals.length > 0 ? message.reveals : message.group).map((r) => <UserIcon address={r} />)}
{message.reveals.length > 0 ? "From " : "From one of "}
{(message.reveals.length > 0
? message.reveals
: message.group
).map((r) => (
<UserIcon key={r} address={r} />
))}
</div>
<div className="text-right text-gray-400">
{message.reveals.length > 0 && 'Not from '}
{message.denials.map((r) => <UserIcon address={r} />)}
{message.reveals.length > 0 && "Not from "}
{message.denials.map((r) => (
<UserIcon key={r} address={r} />
))}
</div>
</div>
</div>

View File

@@ -1,10 +1,10 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"
import Link from "next/link"
import React, { useCallback, useEffect, useState } from "react"
import { useRouter } from "next/router"
import { Buffer } from "buffer"
import { LOCAL_STORAGE_SECRET_KEY } from "utils/localStorage"
import { useRouter } from "next/router"
export default function LoginPage(props: {}) {
const [value, setValue] = useState("")
@@ -14,23 +14,23 @@ export default function LoginPage(props: {}) {
useEffect(() => {
const secret = localStorage.getItem(LOCAL_STORAGE_SECRET_KEY)
if (secret !== null) {
Buffer.from(secret, "hex").toString()
setValue(secret)
}
})
const save = useCallback((secret: Buffer) => {
localStorage.setItem(LOCAL_STORAGE_SECRET_KEY, secret.toString("hex"))
router.push("/connect")
}, [])
const handleLogin = useCallback((value: string) => {
save(Buffer.from(value))
const _ = Buffer.from(value, "hex") // just make sure it's a valid hex string
localStorage.setItem(LOCAL_STORAGE_SECRET_KEY, value)
router.push("/backup")
}, [])
const handleGenerateKey = useCallback(() => {
console.log("generating key")
const array = new Uint8Array(32)
crypto.getRandomValues(array)
save(Buffer.from(array))
const secret = Buffer.from(array).toString("hex")
localStorage.setItem(LOCAL_STORAGE_SECRET_KEY, secret)
router.push("/backup")
}, [])
return (
@@ -38,6 +38,7 @@ export default function LoginPage(props: {}) {
<h1 className="uppercase font-bold pt-16 pb-6">zk chat</h1>
<div className="border border-gray-300 rounded-xl p-6 text-center">
<input
className="p-2"
type="text"
placeholder="Your secret token"
value={value}
@@ -52,11 +53,13 @@ export default function LoginPage(props: {}) {
Login
</button>
or
<Link href="/connect">
<div className="cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-3 text-center">
Sign up (generate a new key)
</div>
</Link>
<button
disabled={value === ""}
onClick={() => handleGenerateKey()}
className="block w-full cursor-pointer bg-pink text-white rounded-xl px-4 py-2 mt-2 mb-3 text-center"
>
Sign up (generate a new secret token)
</button>
</div>
</div>
)