This commit is contained in:
2023-07-08 13:38:57 -04:00
commit bcb715072d
48 changed files with 5456 additions and 0 deletions

13
.eslintignore Normal file
View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

30
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,30 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest

13
.prettierignore Normal file
View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

3979
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

41
package.json Normal file
View File

@@ -0,0 +1,41 @@
{
"name": "discreetly",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write .",
"prepare": "svelte-kit sync"
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^2.1.0",
"@sveltejs/kit": "^1.21.0",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5",
"svelte-check": "^3.4.5",
"svelte-kit": "^1.2.0",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.3.6"
},
"type": "module",
"dependencies": {
"@semaphore-protocol/group": "^3.10.1",
"discreetly-interfaces": "^0.1.0",
"poseidon-lite": "^0.2.0",
"socket.io-client": "^4.7.1"
}
}

12
playwright.config.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
};
export default config;

174
src/app.css Normal file
View File

@@ -0,0 +1,174 @@
:root {
--blackish: rgba(0, 0, 0, 0.98);
--gray-dark: #212121;
--gray-light: rgba(242, 242, 242, 0.6);
--white: rgba(255, 255, 255, 1);
--whitish: rgb(225, 225, 225);
--steel: hsl(184, 18%, 50%);
--steel-rgb: 105, 147, 150;
--steel-white: hsl(185, 28%, 95%);
--steel-very-light: hsl(185, 28%, 88%);
--steel-light: hsl(185, 18%, 66%);
--steel-bright: hsl(185, 75%, 33%);
--steel-bright-rg: 21, 137, 147;
--steel-dark: hsl(185, 18%, 33%);
--steel-night: hsl(185, 12%, 12%);
--neon-green: #59f02b;
--green: #19d457;
--green-light: #61f291;
--green-dark: #198754;
--jade: #45a164;
--hunter-green: #405c37;
--dark-blue: #315db5;
--blue: #477eed;
--blue-light: #53d3e0;
--blue-very-light: #a7f6ff;
--violet: #9198e5;
--violet-light: #b4bbff;
--mauve: #cc71c2;
--pink: #bf2c7f;
--purple: #b638f5;
--sunset: #ff7575;
--sunset-light: #ffb585;
--orangered: #fa5f5f;
--max-red: #de1a1a;
--yellow: #fad14b;
--bs-body-color: var(--steel-night);
--bs-body-color-rgb: 225, 225, 225;
--bs-body-bg: var(--steel-very-light);
--bs-body-bg-rgb: 213, 225, 226;
--bs-emphasis-color: #fff;
--bs-emphasis-color-rgb: 255, 255, 255;
--bs-secondary-color: var(--steel);
--bs-secondary-color-rgb: var(--steel-rgb);
--bs-secondary-bg: var(--whitish);
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(173, 181, 189, 0.5);
--bs-tertiary-color-rgb: 173, 181, 189;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #6ea8fe;
--bs-secondary-text-emphasis: #a7acb1;
--bs-success-text-emphasis: #75b798;
--bs-info-text-emphasis: #6edff6;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ea868f;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #031633;
--bs-secondary-bg-subtle: #161719;
--bs-success-bg-subtle: #051b11;
--bs-info-bg-subtle: #032830;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #2c0b0e;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: var(--steel-light);
--bs-secondary-border-subtle: #41464b;
--bs-success-border-subtle: #0f5132;
--bs-info-border-subtle: #087990;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #842029;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: var(--orangered);
--bs-link-color: #6ea8fe;
--bs-link-hover-color: #8bb9fe;
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
--bs-border-color: var(--steel-dark);
--bs-border-color-translucent: var(--steel-dark);
--bs-form-valid-color: #75b798;
--bs-form-valid-border-color: #75b798;
--bs-form-invalid-color: #ea868f;
--bs-form-invalid-border-color: #ea868f;
--bs-font-sans-serif: 'Space Grotesk', Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", sans-serif;
--bs-font-monospace: 'Space Mono', monospace;
--bs-link-hover-color: var(--steel-bright);
--bs-link-hover-color-rgb: var(--steel-bright-rgb);
}
h1,
h2,
h3,
h4,
h5,
h6 {
background-image: radial-gradient(ellipse, var(--steel-dark), var(--steel-night));
color: transparent;
background-clip: text;
-webkit-background-clip: text;
}
.card-title {
text-shadow: none;
}
.btn-primary {
--bs-btn-color: var(--steel-white);
--bs-btn-bg: var(--steel-bright);
--bs-btn-border-color: var(--steel-dark);
--bs-btn-hover-color: var(--steel-white);
--bs-btn-hover-bg: var(--orangered);
--bs-btn-hover-border-color: var(--max-red);
--bs-btn-focus-shadow-rgb: 49, 132, 253;
--bs-btn-active-color: var(--steel-white);
--bs-btn-active-bg: var(--sunset);
--bs-btn-active-border-color: var(--orangered);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: var(--steel-dark);
--bs-btn-disabled-bg: var(--steel-night);
--bs-btn-disabled-border-color: var(--orangered);
}
.nav {
--bs-nav-link-padding-x: 1rem;
--bs-nav-link-padding-y: 0.5rem;
--bs-nav-link-color: var(--steel-dark);
--bs-nav-link-hover-color: var(--steel-bright);
--bs-nav-link-disabled-color: var(--bs-secondary-color);
}
.nav-link {
color: var(--steel);
}
.nav-underline .nav-link.active,
.nav-underline .show>.nav-link {
color: var(--steel-bright);
}
.nav-link:focus,
.nav-link:hover {
color: var(--steel-bright);
}
a.nav-link:hover,
a.btn:hover {
text-decoration: none;
}
a:hover {
color: var(--steel-bright);
text-decoration: underline;
}
a {
color: var(--steel);
text-decoration: none;
}
input,
textarea {
border: 1px solid var(--steel-dark);
border-radius: 0.5em;
padding: 0.35rem 0.5rem;
background-color: var(--steel-white);
}
input:focus-visible,
textarea:focus-visible {
outline: none;
box-shadow: 0 0 0 0.15em var(--steel-bright);
}

12
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

28
src/app.html Normal file
View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous"
></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- TODO host fonts ourselves to stop the google tracking machine -->
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"
/>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

4
src/hooks.ts Normal file
View File

@@ -0,0 +1,4 @@
// FIXME: This is a potential hack to get proofs to generate on the front end
export async function handle({ event, resolve }) {
return resolve(event, { ssr: false });
}

14
src/lib/button.svelte Normal file
View File

@@ -0,0 +1,14 @@
<script lang="ts">
import type { ButtonI } from './types';
export let button: ButtonI;
</script>
<a
href={button.link}
class="btn btn-lg px-4 d-inline-flex align-items-center {button.class
? button.class
: 'btn-primary'}"
type="button"
>
{button.text}
</a>

18
src/lib/card.svelte Normal file
View File

@@ -0,0 +1,18 @@
<script lang="ts">
import type { ButtonI } from './types';
import Button from './button.svelte';
export let title: string;
export let buttons: ButtonI[] = [];
</script>
<div class="m-4 px-5 py-4 text-center bg-body-tertiary rounded-2">
<h2 class="text-body-emphasis m-3">{title}</h2>
<div class="col-lg-8 mx-auto fs-5 text-muted">
<slot />
</div>
<div class="d-inline-flex gap-5 my-3">
{#each buttons as button}
<Button {button} />
{/each}
</div>
</div>

15
src/lib/rooms.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { RoomGroupI } from 'discreetly-interfaces';
export const roomGroups: RoomGroupI[] = [
{
name: 'Loading...',
rooms: [
{
id: BigInt(0),
name: 'Loading Rooms',
membership: {
identityCommitments: [BigInt(0)]
}
}
]
}
];

61
src/lib/stores.ts Normal file
View File

@@ -0,0 +1,61 @@
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
// This is just a list of endpoints of servers to connect to, no other information, this is mainly for bootstraping the app
export const serverListStore = storable(['http://localhost:3001/api/'], 'servers');
// This is what gets populated after querying the serverListStore, with the server's information, public rooms available, etc.
export const serverDataStore = storable({}, 'serverData');
// JUST an index to the serverDataStore, so we can keep track of which server we're currently connected to
export const selectedServer = storable({}, 'selectedServer');
// Session store (removed after the session is cleared) of the last 500 messages or so of each room the user participates in; rooms they don't have selected will not be updated
export const messageStore = sessionable({}, 'messages');
// Stores the user's identity // TODO THIS NEEDS TO BE AN ENCRYPTED SEMAPHORE IDENTITY IN THE FUTURE
export const identityStore = storable({}, 'identity');
export function storable(data: any, storagePath = 'storable') {
const store = writable(data);
const { subscribe, set, update } = store;
const isBrowser = typeof window !== 'undefined';
isBrowser && localStorage[storagePath] && set(JSON.parse(localStorage[storagePath]));
return {
subscribe,
set: (n: object | string | number) => {
browser && (localStorage[storagePath] = JSON.stringify(n));
set(n);
},
update: (cb) => {
const updatedStore = cb(get(store));
browser && (localStorage[storagePath] = JSON.stringify(updatedStore));
set(updatedStore);
}
};
}
export function sessionable(data: any, storagePath = 'storable') {
const store = writable(data);
const { subscribe, set, update } = store;
const isBrowser = typeof window !== 'undefined';
isBrowser && sessionStorage[storagePath] && set(JSON.parse(sessionStorage[storagePath]));
return {
subscribe,
set: (n: object | string | number) => {
browser && (sessionStorage[storagePath] = JSON.stringify(n));
set(n);
},
update: (cb) => {
const updatedStore = cb(get(store));
browser && (sessionStorage[storagePath] = JSON.stringify(updatedStore));
set(updatedStore);
}
};
}

7
src/lib/types.ts Normal file
View File

@@ -0,0 +1,7 @@
interface ButtonI {
link: string;
text: string;
class?: string;
}
export type { ButtonI };

68
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,68 @@
import type { Identity } from '@semaphore-protocol/identity';
import type { MessageI, RoomI, ServerI } from 'discreetly-interfaces';
import { randomBigInt, genId, str2BigInt } from 'discreetly-interfaces';
import { RLNProver, type RLNFullProof, type MerkleProof } from 'rlnjs';
import { poseidon1 } from 'poseidon-lite/poseidon1';
import { Group } from '@semaphore-protocol/group';
import { poseidon2 } from 'poseidon-lite/poseidon2';
const wasmPath = '/rln/circuit.wasm';
const zkeyPath = '/rln/final.zkey';
const prover: RLNProver = new RLNProver(wasmPath, zkeyPath);
interface proofInputsI {
rlnIdentifier: bigint;
identitySecret: bigint;
userMessageLimit: bigint;
messageId: bigint;
merkleProof: MerkleProof;
x: bigint;
epoch: bigint;
}
async function genProof(room: RoomI, message: string, identity: Identity): Promise<MessageI> {
const userMessageLimit = BigInt(1);
const messageHash: bigint = poseidon1([str2BigInt(message)]);
const group = new Group(room.id, 20, room.membership?.identityCommitments);
const rateCommitment: bigint = poseidon2([identity.getCommitment(), userMessageLimit]);
group.addMember(rateCommitment); // FIXME: This is just a hack to add the user to the group for testing
const proofInputs: proofInputsI = {
rlnIdentifier: BigInt(room.id),
identitySecret: identity.getSecret(),
userMessageLimit: userMessageLimit,
messageId: BigInt(0),
merkleProof: group.generateMerkleProof(group.indexOf(rateCommitment)),
x: messageHash,
epoch: BigInt(Date.now().toString())
};
//console.debug('PROOFINPUTS:', proofInputs);
return prover.generateProof(proofInputs).then((proof: RLNFullProof) => {
console.log('Proof generated!');
const msg: MessageI = {
id: proof.snarkProof.publicSignals.nullifier.toString(),
message: message,
room: BigInt(proof.snarkProof.publicSignals.externalNullifier),
proof
};
return msg;
});
}
async function fetchServer(server_url: string): Promise<ServerI | void> {
console.debug(`Fetching server ${server_url}`);
return fetch(server_url, {
method: 'GET',
headers: {
'Access-Control-Allow-Origin': 'http://localhost:*'
}
})
.then(async (response): Promise<ServerI> => {
const serverData: ServerI = await response.json();
return serverData;
})
.catch((err) => {
console.error(err);
});
}
export { genProof, randomBigInt, genId, fetchServer };

8
src/routes/+error.svelte Normal file
View File

@@ -0,0 +1,8 @@
<script>
import { error } from '@sveltejs/kit';
</script>
<div class="container-fluid w-75 my-3">
<h3>Error!</h3>
<p>{error}</p>
</div>

55
src/routes/+layout.svelte Normal file
View File

@@ -0,0 +1,55 @@
<script lang="ts">
import '../app.css';
import { onMount } from 'svelte';
import AppHeader from './AppHeader.svelte';
import AppFooter from './AppFooter.svelte';
import { identityStore, serverListStore, serverDataStore, selectedServer } from '$lib/stores';
import { Identity } from '@semaphore-protocol/identity';
import type { ServerI } from 'discreetly-interfaces';
import { fetchServer } from '$lib/utils';
(BigInt.prototype as any).toJSON = function () {
return this.toString();
};
function setSelectedServer(server: number) {
console.debug('setting selected server');
selectedServer.set(server);
}
onMount(async () => {
$serverListStore.forEach((server: string) => {
console.log('fetching server data');
fetchServer(server).then((data) => {
console.log('setting server data');
console.log(data);
console.log(server);
$serverDataStore[server] = data as ServerI;
console.log($serverDataStore);
});
});
if ($selectedServer.name == undefined) {
$selectedServer = $serverListStore[0];
}
});
// TODO THIS IS ONLY FOR DEVELOPMENT AND SHOULD BE REMOVED AFTER SIGNUP IS SETUP
if (!$identityStore['_nullifier']) {
console.log('MAKING UP SECRETS');
$identityStore = new Identity();
}
</script>
<div class="d-flex flex-column align-content-between">
<AppHeader {setSelectedServer} />
<main class="container-fluid align-items-center align-self-stretch">
<slot />
</main>
<AppFooter />
</div>
<style>
main {
margin-top: calc(52px);
}
</style>

4
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,4 @@
<script lang="ts">
</script>
<h2>HOMEPAGE</h2>

View File

@@ -0,0 +1,14 @@
<div class="container-fluid">
<footer class="py-3 my-4">
<ul class="nav justify-content-center border-bottom pb-3 mb-3">
<li class="nav-item"><a href="/" class="nav-link px-2 text-body-secondary">Home</a></li>
<li class="nav-item">
<a href="/signup" class="nav-link px-2 text-body-secondary">Signup</a>
</li>
<li class="nav-item"><a href="/login" class="nav-link px-2 text-body-secondary">Login</a></li>
<li class="nav-item"><a href="/chat" class="nav-link px-2 text-body-secondary">Chat</a></li>
<li class="nav-item"><a href="/about" class="nav-link px-2 text-body-secondary">About</a></li>
</ul>
<p class="text-center text-body-secondary">© 2023 Privacy & Scaling Explorations</p>
</footer>
</div>

113
src/routes/AppHeader.svelte Normal file
View File

@@ -0,0 +1,113 @@
<script lang="ts">
import { serverDataStore, selectedServer, serverListStore } from '$lib/stores';
export let setSelectedServer: (server: number) => void;
</script>
<header>
<nav class="navbar fixed-top navbar-dark bg-dark navbar-expand-lg">
<div class="container-fluid d-flex align-content-between">
<div class="d-flex">
<a class="navbar-brand d-none d-md-block" href="/">Discreetly</a>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<a class="navbar-brand d-block d-md-none" href="/">Discreetly</a>
<li class="nav-item">
<a href="/" class="nav-link">Home</a>
</li>
<li class="nav-item">
<a href="/chat" class="nav-link">Chat</a>
</li>
<li class="nav-item d-block d-lg-none">
<a href="/signup" class="nav-link">Signup</a>
</li>
<li class="nav-item d-block d-lg-none">
<a href="/login" class="nav-link">Login</a>
</li>
</ul>
</div>
</div>
{#if $serverDataStore[$selectedServer] != undefined}
<div class="navbar-brand dropdown" id="server-title">
<a
class="nav-link dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
title={String($serverDataStore[$selectedServer].name + ' (' + $selectedServer + ')')}
>
{$serverDataStore[$selectedServer].name}
</a>
<ul class="dropdown-menu">
{#each $serverListStore as key}
<li>
<div
aria-label={'select ' + $serverDataStore[key].name}
class="dropdown-item"
on:click={() => setSelectedServer(key)}
on:keydown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
setSelectedServer(key);
}
}}
role="button"
tabindex="0"
title={String($serverDataStore[key].name + ' (' + key + ')')}
>
{$serverDataStore[key].name}
</div>
</li>
{/each}
</ul>
</div>
{/if}
<div>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item d-none d-lg-block">
<a href="/signup" class="nav-link">Signup</a>
</li>
<li class="nav-item d-none d-lg-block">
<a href="/login" class="nav-link">Login</a>
</li>
</ul>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarText"
aria-controls="navbarText"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon" />
</button>
</div>
</div>
</nav>
</header>
<style>
.navbar-dark .navbar-brand {
color: var(--orangered);
}
#server-title {
font-family: 'Space Mono';
color: var(--steel-light);
}
.nav-link {
color: var(--steel);
text-decoration: none;
}
.nav-link:hover {
color: var(--steel-bright);
text-decoration: none;
}
.dropdown-item:focus,
.dropdown-item:hover {
color: var(--blackish);
background-color: var(--steel-bright);
}
</style>

View File

@@ -0,0 +1 @@
Read more about <a href="https://rate-limiting-nullifier.github.io/rln-docs/">RLN</a>.

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import RoomList from './RoomList.svelte';
import ChatRoom from './ChatRoom.svelte';
import type { RoomGroupI, RoomI } from 'discreetly-interfaces';
import { serverDataStore, selectedServer } from '$lib/stores';
import { onMount } from 'svelte';
let room: RoomI;
let loaded: Boolean = false;
function selectRoom(id: RoomI['id']) {
$serverDataStore[$selectedServer].selectedRoom = id;
setRoom(id as string);
}
function setRoom(id: string) {
const rooms = $serverDataStore[$selectedServer].roomGroups;
const temp_room = rooms
.map((group: RoomGroupI) => group.rooms)
.flat()
.find((room: RoomI) => room.id === id);
if (temp_room) {
console.debug('Setting Room to Selected', temp_room.name);
room = temp_room;
} else if ($serverDataStore[$selectedServer].roomGroups[0]) {
console.debug('Setting Room to Default');
room = $serverDataStore[$selectedServer].roomGroups[0].rooms[0];
} else {
console.debug('Loading Rooms Still');
room = {
id: '0',
name: 'Rooms Not Loaded',
membership: { identityCommitments: [0n] }
};
}
}
onMount(() => {
setRoom($serverDataStore[$selectedServer].selectedRoom as string);
loaded = true;
});
</script>
<div class="container-fluid mt-2">
<div class="row">
{#if $serverDataStore[$selectedServer] && loaded}
<RoomList {selectRoom} />
{:else}
<div class="col-12">
<div class="alert alert-info" role="alert">Loading Room List...</div>
</div>
{/if}
{#if room && loaded}
<ChatRoom {room} />
{:else}
<slot />
{/if}
</div>
</div>

View File

@@ -0,0 +1,4 @@
<script lang="ts">
</script>
<div class="col">Loading Server Rooms...</div>

View File

@@ -0,0 +1,166 @@
<script lang="ts">
import { identityStore, selectedServer, messageStore, serverDataStore } from '$lib/stores';
import type { RoomI, MessageI } from 'discreetly-interfaces';
import { io } from 'socket.io-client';
import { onDestroy } from 'svelte';
import { genProof } from '$lib/utils';
import { Identity } from '@semaphore-protocol/identity';
export let room: RoomI;
if (!$messageStore[room.id.toString()]) {
$messageStore[room.id.toString()] = [];
}
let messages = [...$messageStore[room.id.toString()]];
onDestroy(() => {
socket.emit('leavingRoom', room?.id);
socket.disconnect();
});
let inputText = '';
let sendButtonText = 'Send';
const socketURL: string = $serverDataStore[$selectedServer].messageHandlerSocket || '';
const socket = io(socketURL);
let connected: boolean = false;
socket.on('connect', () => {
connected = true;
const engine = socket.io.engine;
engine.once('upgrade', () => {
console.debug('Upgraded connection to', engine.transport.name);
});
engine.on('close', (reason) => {
console.debug('socket-io-transport-closed', reason);
});
socket.emit('joiningRoom', room?.id);
});
socket.on('disconnected', () => {
connected = false;
console.debug('disconnected');
});
socket.on('connect_error', (err) => {
console.debug('chat connection error', err.message);
});
socket.on('connect_timeout', (err) => {
console.debug('chat connection timeout', err.message);
});
socket.on('error', (err) => {
console.debug('chat websocket error', err.message);
});
socket.on('messageBroadcast', (data: MessageI) => {
messages = [data, ...messages];
messages = messages.slice(0, 500);
$messageStore[room.id.toString()] = messages;
});
function sendMessage(message: string) {
const identity = new Identity($identityStore.toString());
genProof(room, message, identity).then((msg) => {
socket.emit('validateMessage', msg);
});
}
</script>
<div class="col chat-room">
<h3 class="d-flex justify-content-between align-content-center">
{room?.name}
<span class="fs-6 fw-light align-self-center" style="color:gray">
{#if connected}
<div aria-label="Connected">🟢</div>
{:else}
<div aria-label="Disconnected">🔴</div>
{/if}
</span>
</h3>
<div id="messages" class="mb-3">
<section>
{#each messages as message}
<div class="msg">
<div class="msg-id">{message.id}</div>
<span class="msg-text">{message.message}</span>
</div>
{/each}
</section>
</div>
<div id="chat-input">
<input
type="text"
placeholder="Type your message here"
bind:value={inputText}
on:keydown={(event) => {
if (event.key === 'Enter') {
sendMessage(inputText);
inputText = '';
}
}}
/>
<div
class="btn btn-primary"
on:click={() => {
sendMessage(inputText);
inputText = '';
}}
>
{sendButtonText}
</div>
</div>
</div>
<style>
#messages {
border: 1px solid var(--steel-dark);
border-radius: 0.5em;
padding: 0.35rem 0.5rem;
background-color: var(--steel-white);
}
#messages section {
overflow-y: scroll;
max-height: 60vh;
display: flex;
flex-direction: column-reverse;
gap: 0.5rem;
}
.msg {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.msg-id {
font-family: 'Space Mono', monospace;
color: var(--steel-dark);
width: 12ch;
text-overflow: ellipsis;
overflow: hidden;
}
.msg-text::before {
content: ':';
color: var(--steel);
margin-right: 0.5rem;
}
#chat-input {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
}
#chat-input input {
flex-grow: 1;
}
</style>

View File

@@ -0,0 +1,48 @@
<script lang="ts">
import type { RoomGroupI, RoomI } from 'discreetly-interfaces';
import { selectedServer, serverDataStore } from '$lib/stores';
$: roomGroups = $serverDataStore[$selectedServer].roomGroups;
export let selectRoom: (roomId: RoomI['id']) => any;
function getMembers(room: RoomI): string {
let total: number | string = '0';
total = room.membership?.identityCommitments?.length || '?';
return total.toString();
}
</script>
<div class="col-3">
<section id="roomList">
{#each roomGroups as group}
<h4 class="mb-2 pb-2 border-bottom">{group.name}</h4>
<ul class="list-group my-2">
{#each group.rooms as room, index}
<div class="mb-2">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="d-flex justify-content-between align-items-center">
<h5 class="flex-grow-1">{room.name}</h5>
<div class="px-3">{getMembers(room)} Members</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
on:click={selectRoom(room.id)}
class="btn btn-sm btn-primary d-flex align-items-center"
>
💬
</div>
</div>
</div>
{/each}
</ul>
{/each}
</section>
</div>
<style>
.btn-sm {
text-align: center !important;
}
</style>

View File

@@ -0,0 +1,53 @@
<script lang="ts">
import { page } from '$app/stores';
import { identityStore } from '$lib/stores';
// TODO Check if Identity is created
// TODO Check if Gates exist
</script>
<ul class="nav nav-underline d-flex justify-content-evenly mt-3 mb-4">
<li class="nav-item">
<a
class="nav-link"
class:active={!$page.route.id?.match(/gates|ceremony|backup/g)}
class:complete={$page.route.id?.match(/gates|ceremony|backup/g)}
aria-current="page"
href="/signup">Sign Up</a
>
</li>
<li class="nav-item">
<a
class="nav-link"
href="/signup/gates"
class:active={$page.route.id?.match(/gates/g)}
class:complete={$page.route.id?.match(/ceremony|backup/g)}>Choose Communities</a
>
</li>
<li class="nav-item">
<a
class="nav-link"
href="/signup/ceremony"
class:complete={$page.route.id?.match(/backup/g)}
class:active={$page.route.id?.match(/ceremony/g)}>Create Identity</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="/signup/backup" class:active={$page.route.id?.match(/backup/g)}
>Backup Identity</a
>
</li>
</ul>
<div class="container-fluid">
<div class="d-flex flex-column align-items-center">
<slot />
</div>
</div>
<style>
.complete::after {
content: '✓';
padding-left: 0.5rem;
}
</style>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import Card from '$lib/card.svelte';
let links = [
{
link: '/about',
text: 'Read More',
class: 'btn-secondary'
},
{
link: '/signup/gates',
text: 'Continue ➡',
class: 'btn-primary'
}
];
</script>
<Card title="Welcome to Discreetly" buttons={links}>
This app is a little different from what you're used to.
</Card>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import Card from '$lib/card.svelte';
let links = [
{
link: '/chat',
text: 'Start Chatting ➡',
class: 'btn-primary'
}
];
</script>
<Card title="Backup Your Identity" buttons={links}>TODO</Card>

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import Card from '$lib/card.svelte';
let links = [
{
link: '/chat',
text: 'Skip Backup',
class: 'btn-secondary'
},
{
link: '/signup/backup',
text: 'Backup Identity ➡',
class: 'btn-primary'
}
];
</script>
<Card title="Generate Your Identity" buttons={links}>TODO</Card>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import Card from '$lib/card.svelte';
let links = [
{
link: '/signup/ceremony',
text: 'Continue ➡',
class: 'btn-primary'
}
];
let code = '';
function addCode(code: string) {
console.log(code);
}
</script>
<Card title="Join Communities">
Invite Code: <input
type="text"
placeholder="Invite Code"
bind:value={code}
on:keydown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
addCode(code);
code = '';
} else if (event.key === ' ' || event.key === '-') {
event.preventDefault();
if (code.length > 0 && code[code.length - 1] !== '-') {
code += '-';
}
}
}}
/>
</Card>

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,17 @@
[Circuit_Version]
RLN_Version = 2
RLN_Type = "rln"
[Circuit_Build]
Circom_Version = "circom compiler 2.1.5"
GitHub_URL = "https://github.com/Rate-Limiting-Nullifier/rln-circuits-v2.git"
Git_Commit = "17f0fed"
Compilation_Time = 1688818882
[Files]
Wasm = "circuit.wasm"
Wasm_SHA256SUM = "fa9586db68a9566fd9b3af6e8d7c66f5567b35647aa63a426f220375e9fa8c04"
Zkey = "final.zkey"
Zkey_SHA256SUM = "5eaad7aae4dcc269e7de5b4e1aad7578cc3316ce1e2923c72c0eacf8d8a7c3c1"
Verification_Key = "verification_key.json"
Verification_Key_SHA256SUM = "6a45165b480239cac8e2de450f488e37198a18297db55a9509589c14b7db59f7"

BIN
static/rln/circuit.wasm Normal file

Binary file not shown.

BIN
static/rln/final.zkey Normal file

Binary file not shown.

View File

@@ -0,0 +1,114 @@
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": 5,
"vk_alpha_1": [
"20491192805390485299153009773594534940189261866228447918068658471970481763042",
"9383485363053290200918347156157836566562967994039712273449902621266178545958",
"1"
],
"vk_beta_2": [
[
"6375614351688725206403948262868962793625744043794305715222011528459656738731",
"4252822878758300859123897981450591353533073413197771768651442665752259397132"
],
[
"10505242626370262277552901082094356697409835680220590971873171140371331206856",
"21847035105528745403288232691147584728191162732299865338377159692350059136679"
],
[
"1",
"0"
]
],
"vk_gamma_2": [
[
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
],
[
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
],
[
"1",
"0"
]
],
"vk_delta_2": [
[
"3817638527075299341910923013750491829772293424386698324526317429044327254930",
"16817041674734304676071688134655235316477394149172545683919861305510126014325"
],
[
"15321616806892601534034512759307826486280009165298528953245727688557811743473",
"6325777744419158965546141708181579937852352378408429766464847802592525079966"
],
[
"1",
"0"
]
],
"vk_alphabeta_12": [
[
[
"2029413683389138792403550203267699914886160938906632433982220835551125967885",
"21072700047562757817161031222997517981543347628379360635925549008442030252106"
],
[
"5940354580057074848093997050200682056184807770593307860589430076672439820312",
"12156638873931618554171829126792193045421052652279363021382169897324752428276"
],
[
"7898200236362823042373859371574133993780991612861777490112507062703164551277",
"7074218545237549455313236346927434013100842096812539264420499035217050630853"
]
],
[
[
"7077479683546002997211712695946002074877511277312570035766170199895071832130",
"10093483419865920389913245021038182291233451549023025229112148274109565435465"
],
[
"4595479056700221319381530156280926371456704509942304414423590385166031118820",
"19831328484489333784475432780421641293929726139240675179672856274388269393268"
],
[
"11934129596455521040620786944827826205713621633706285934057045369193958244500",
"8037395052364110730298837004334506829870972346962140206007064471173334027475"
]
]
],
"IC": [
[
"4920513730204767532050733107749276406754520419375654722016092399980613788208",
"10950491564509418434657706642388934308456795265036074733953533582377584967294",
"1"
],
[
"6815064660695497986531118446154820702646540722664044216580897159556261271171",
"17838140936832571103329556013529166877877534025488014783346458943575275015438",
"1"
],
[
"16364982450206976302246609763791333525052810246590359380676749324389440643932",
"17092624338100676284548565502349491320314889021833923882585524649862570629227",
"1"
],
[
"3679639231485547795420532910726924727560917141402837495597760107842698404034",
"16213191511474848247596810551723578773353083440353745908057321946068926848382",
"1"
],
[
"9215428431027260354679105025212521481930206886203677270216204485256690813172",
"934602510541226149881779979217731465262250233587980565969044391353665291792",
"1"
],
[
"8935861545794299876685457331391349387048184820319250771243971382360441890897",
"4993459033694759724715904486381952906869986989682015547152342336961693234616",
"1"
]
]
}

View File

@@ -0,0 +1,17 @@
[Circuit_Version]
RLN_Version = 2
RLN_Type = "withdraw"
[Circuit_Build]
Circom_Version = "circom compiler 2.1.5"
GitHub_URL = "https://github.com/Rate-Limiting-Nullifier/rln-circuits-v2.git"
Git_Commit = "17f0fed"
Compilation_Time = 1688818887
[Files]
Wasm = "circuit.wasm"
Wasm_SHA256SUM = "239bd578deea5eebf3cde5a9aeba22ba799d23d0aff6b9a8b153afd8d2cc191e"
Zkey = "final.zkey"
Zkey_SHA256SUM = "397b769ddcf0bd918e5a85566e978150917da38020ab8e9ce878a6492bf2bca8"
Verification_Key = "verification_key.json"
Verification_Key_SHA256SUM = "a00c881245625369f18593dcd796188f457bd9cb38c6772e662febf2faec6160"

Binary file not shown.

BIN
static/withdraw/final.zkey Normal file

Binary file not shown.

View File

@@ -0,0 +1,99 @@
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": 2,
"vk_alpha_1": [
"20491192805390485299153009773594534940189261866228447918068658471970481763042",
"9383485363053290200918347156157836566562967994039712273449902621266178545958",
"1"
],
"vk_beta_2": [
[
"6375614351688725206403948262868962793625744043794305715222011528459656738731",
"4252822878758300859123897981450591353533073413197771768651442665752259397132"
],
[
"10505242626370262277552901082094356697409835680220590971873171140371331206856",
"21847035105528745403288232691147584728191162732299865338377159692350059136679"
],
[
"1",
"0"
]
],
"vk_gamma_2": [
[
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
],
[
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
],
[
"1",
"0"
]
],
"vk_delta_2": [
[
"11983007890138271431646631339128325393343025477496193427884097571341594637227",
"7369399572723042520302573420729576910581887995143697476970345965043452827346"
],
[
"18498623948725074905368922260006856461276039836160071250761949723295691187028",
"20253812994897318378765322974844822348210001364477588185294466023467389614920"
],
[
"1",
"0"
]
],
"vk_alphabeta_12": [
[
[
"2029413683389138792403550203267699914886160938906632433982220835551125967885",
"21072700047562757817161031222997517981543347628379360635925549008442030252106"
],
[
"5940354580057074848093997050200682056184807770593307860589430076672439820312",
"12156638873931618554171829126792193045421052652279363021382169897324752428276"
],
[
"7898200236362823042373859371574133993780991612861777490112507062703164551277",
"7074218545237549455313236346927434013100842096812539264420499035217050630853"
]
],
[
[
"7077479683546002997211712695946002074877511277312570035766170199895071832130",
"10093483419865920389913245021038182291233451549023025229112148274109565435465"
],
[
"4595479056700221319381530156280926371456704509942304414423590385166031118820",
"19831328484489333784475432780421641293929726139240675179672856274388269393268"
],
[
"11934129596455521040620786944827826205713621633706285934057045369193958244500",
"8037395052364110730298837004334506829870972346962140206007064471173334027475"
]
]
],
"IC": [
[
"19490069286251317200471893224761952280235157078692599655063040494106083015102",
"15613730057977833735664106983317680013118142165231654768046521650638333652991",
"1"
],
[
"1563543155852853229359605494188815884199915022658219002707722789976065966419",
"858819375930654753672617171465307097688802650498051619587167586479724200799",
"1"
],
[
"3808889614445935800597561392085733302718838702771107544944545050886958022904",
"13293649293049947010793838294353767499934999769633605908974566715226392122400",
"1"
]
]
}

21
svelte.config.js Normal file
View File

@@ -0,0 +1,21 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
},
build: {
sourcemap: true // Config vite to generate sourcemap when bundling.
},
};
export default config;

6
tests/test.ts Normal file
View File

@@ -0,0 +1,6 @@
import { expect, test } from '@playwright/test';
test('index page has expected h1', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
});

17
tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

6
vite.config.ts Normal file
View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});