mirror of
https://github.com/status-im/js-waku-examples.git
synced 2025-01-27 15:24:52 +00:00
add rln-identity example, fix minor stuff in rln-js
This commit is contained in:
parent
698afe4dab
commit
75438f31df
BIN
examples/rln-identity/favicon.ico
Normal file
BIN
examples/rln-identity/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
examples/rln-identity/favicon.png
Normal file
BIN
examples/rln-identity/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
201
examples/rln-identity/index.html
Normal file
201
examples/rln-identity/index.html
Normal file
@ -0,0 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>RLN Credential management</title>
|
||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
<link rel="icon" href="./favicon.ico" />
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
word-wrap: break-word;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
min-width: 300px;
|
||||
max-width: 800px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: space-between;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h3:last-of-type {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h2 span,
|
||||
h3 span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.progress {
|
||||
color: #9ea13b;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #3ba183;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #c84740;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 0.5rem;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
button.progress {
|
||||
color: white;
|
||||
background-color: #9ea13b;
|
||||
}
|
||||
|
||||
button.success {
|
||||
color: white;
|
||||
background-color: #3ba183;
|
||||
}
|
||||
|
||||
button.error {
|
||||
color: white;
|
||||
background-color: #c84740;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.mb-2 {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.mb-3 {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.mt-1 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="status">
|
||||
<h3>
|
||||
<b>Status:</b>
|
||||
<span id="status" class="progress">Starting...</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<h2>Wallet</h2>
|
||||
<button id="connect">Connect</button>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<h2>Keystore</h2>
|
||||
<div>
|
||||
<button id="import">Import</button>
|
||||
<button id="export">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h3 class="mt-1">Existing credentials</h3>
|
||||
|
||||
<div class="block mb-2">
|
||||
<select id="keystore"></select>
|
||||
<div>
|
||||
<input id="password" placeholder="password" />
|
||||
<button id="read-credential">Read</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block mb-3">
|
||||
<h3>Create new (will use the password)</h3>
|
||||
<button id="register-new">Register</button>
|
||||
</div>
|
||||
|
||||
<div id="current-credentials">
|
||||
<div class="block mb-1">
|
||||
<p>Keystore hash</p>
|
||||
<code>none</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Membership ID</p>
|
||||
<code>none</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Secret Hash</p>
|
||||
<code>none</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Commitment</p>
|
||||
<code>none</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Nullifier</p>
|
||||
<code>none</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Trapdoor</p>
|
||||
<code>none</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
19
examples/rln-identity/manifest.json
Normal file
19
examples/rln-identity/manifest.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Waku RLN",
|
||||
"description": "Example showing Waku RLN credential management.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "favicon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"display": "standalone",
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff"
|
||||
}
|
17494
examples/rln-identity/package-lock.json
generated
Normal file
17494
examples/rln-identity/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
examples/rln-identity/package.json
Normal file
24
examples/rln-identity/package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "rln-chat",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"start": "webpack-dev-server"
|
||||
},
|
||||
"dependencies": {
|
||||
"@waku/rln": "0.1.1-bafbe01",
|
||||
"@waku/sdk": "^0.0.22",
|
||||
"@waku/utils": "^0.0.14",
|
||||
"ethers": "^5.7.2",
|
||||
"multiaddr": "^10.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "13.5.6",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
}
|
||||
}
|
2
examples/rln-identity/src/const.js
Normal file
2
examples/rln-identity/src/const.js
Normal file
@ -0,0 +1,2 @@
|
||||
export const SIGNATURE_MESSAGE =
|
||||
"The signature of this message will be used to generate your RLN credentials. Anyone accessing it may send messages on your behalf, please only share with the RLN dApp";
|
27
examples/rln-identity/src/index.js
Normal file
27
examples/rln-identity/src/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { initUI } from "./ui";
|
||||
import { initRLN } from "./rln";
|
||||
|
||||
async function run() {
|
||||
const { registerEvents, onStatusChange } = initUI();
|
||||
const {
|
||||
connectWallet,
|
||||
registerCredential,
|
||||
readKeystoreOptions,
|
||||
readCredential,
|
||||
saveLocalKeystore,
|
||||
importLocalKeystore,
|
||||
} = await initRLN({
|
||||
onStatusChange,
|
||||
});
|
||||
|
||||
registerEvents({
|
||||
connectWallet,
|
||||
registerCredential,
|
||||
readKeystoreOptions,
|
||||
readCredential,
|
||||
saveLocalKeystore,
|
||||
importLocalKeystore,
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
89
examples/rln-identity/src/rln.js
Normal file
89
examples/rln-identity/src/rln.js
Normal file
@ -0,0 +1,89 @@
|
||||
import { createRLN, Keystore } from "@waku/rln";
|
||||
import { randomNumber } from "./utils";
|
||||
import { SIGNATURE_MESSAGE } from "./const";
|
||||
|
||||
export async function initRLN({ onStatusChange }) {
|
||||
onStatusChange("Initializing RLN...");
|
||||
|
||||
let rln;
|
||||
try {
|
||||
rln = await createRLN();
|
||||
} catch (err) {
|
||||
onStatusChange(`Failed to initialize RLN: ${err}`, "error");
|
||||
throw Error(err);
|
||||
}
|
||||
|
||||
onStatusChange("RLN initialized", "success");
|
||||
|
||||
const connectWallet = async () => {
|
||||
let signer;
|
||||
try {
|
||||
onStatusChange("Connecting to wallet...");
|
||||
signer = await extractMetaMaskSigner();
|
||||
} catch (err) {
|
||||
onStatusChange(`Failed to access MetaMask: ${err}`, "error");
|
||||
throw Error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
onStatusChange("Connecting to Ethereum...");
|
||||
const localKeystore = readLocalKeystore();
|
||||
rln.keystore = Keystore.fromString(localKeystore);
|
||||
|
||||
await rln.start({ signer });
|
||||
} catch (err) {
|
||||
onStatusChange(`Failed to connect to Ethereum: ${err}`, "error");
|
||||
throw Error(err);
|
||||
}
|
||||
|
||||
onStatusChange("RLN started", "success");
|
||||
};
|
||||
|
||||
const registerCredential = async (password) => {
|
||||
if (!rln.signer) {
|
||||
alert("RLN is not initialized. Try connecting wallet first.");
|
||||
return;
|
||||
}
|
||||
|
||||
const signature = await rln.signer.signMessage(
|
||||
`${SIGNATURE_MESSAGE}. Nonce: ${randomNumber()}`
|
||||
);
|
||||
|
||||
const credential = await rln.registerMembership({ signature });
|
||||
const hash = await rln.keystore.addCredential(credential, password);
|
||||
|
||||
return { hash, credential };
|
||||
};
|
||||
|
||||
const readKeystoreOptions = () => {
|
||||
return rln.keystore.keys();
|
||||
};
|
||||
|
||||
const readCredential = async (hash, password) => {
|
||||
return rln.keystore.readCredential(hash, password);
|
||||
};
|
||||
|
||||
const saveLocalKeystore = () => {
|
||||
const keystoreStr = rln.keystore.toString();
|
||||
localStorage.setItem("keystore", keystoreStr);
|
||||
return keystoreStr;
|
||||
};
|
||||
|
||||
const importLocalKeystore = (keystoreStr) => {
|
||||
rln.keystore = Keystore.fromString(keystoreStr);
|
||||
};
|
||||
|
||||
return {
|
||||
rln,
|
||||
connectWallet,
|
||||
registerCredential,
|
||||
readKeystoreOptions,
|
||||
readCredential,
|
||||
saveLocalKeystore,
|
||||
importLocalKeystore,
|
||||
};
|
||||
}
|
||||
|
||||
function readLocalKeystore() {
|
||||
return localStorage.getItem("keystore") || "";
|
||||
}
|
117
examples/rln-identity/src/ui.js
Normal file
117
examples/rln-identity/src/ui.js
Normal file
@ -0,0 +1,117 @@
|
||||
import { renderBytes } from "./utils";
|
||||
|
||||
const status = document.getElementById("status");
|
||||
const connectWalletButton = document.getElementById("connect");
|
||||
const importKeystore = document.getElementById("import");
|
||||
const exportKeystore = document.getElementById("export");
|
||||
const keystoreOptions = document.getElementById("keystore");
|
||||
const keystorePassword = document.getElementById("password");
|
||||
const readCredentialButton = document.getElementById("read-credential");
|
||||
const registerNewCredentialButton = document.getElementById("register-new");
|
||||
const currentCredentials = document.getElementById("current-credentials");
|
||||
|
||||
export function initUI() {
|
||||
const _renderCredential = (hash, credential) => {
|
||||
currentCredentials.innerHTML = `
|
||||
<div class="block mb-1">
|
||||
<p>Keystore hash</p>
|
||||
<code>${hash || "none"}</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Membership ID</p>
|
||||
<code>${credential.membership.treeIndex || "none"}</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Secret Hash</p>
|
||||
<code>${renderBytes(credential.identity.IDSecretHash)}</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Commitment</p>
|
||||
<code>${renderBytes(credential.identity.IDCommitment)}</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Nullifier</p>
|
||||
<code>${renderBytes(credential.identity.IDNullifier)}</code>
|
||||
</div>
|
||||
|
||||
<div class="block mb-1">
|
||||
<p>Trapdoor</p>
|
||||
<code>${renderBytes(credential.identity.IDTrapdoor)}</code>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const _renderKeystoreOptions = (options) => {
|
||||
keystoreOptions.innerHTML = `
|
||||
${options.map((v) => `<option value=${v}>${v}</option>`)}
|
||||
`;
|
||||
};
|
||||
|
||||
const registerEvents = ({
|
||||
connectWallet,
|
||||
registerCredential,
|
||||
readKeystoreOptions,
|
||||
readCredential,
|
||||
saveLocalKeystore,
|
||||
importLocalKeystore,
|
||||
}) => {
|
||||
connectWalletButton.addEventListener("click", async () => {
|
||||
await connectWallet();
|
||||
const keystoreKeys = readKeystoreOptions();
|
||||
_renderKeystoreOptions(keystoreKeys);
|
||||
});
|
||||
|
||||
registerNewCredentialButton.addEventListener("click", async () => {
|
||||
const password = keystorePassword.value;
|
||||
|
||||
if (!password) {
|
||||
alert("Please, input password in order to create new credentials.");
|
||||
return;
|
||||
}
|
||||
|
||||
const { hash, credential } = await registerCredential(password);
|
||||
_renderCredential(hash, credential);
|
||||
|
||||
const keystoreKeys = readKeystoreOptions();
|
||||
_renderKeystoreOptions(keystoreKeys);
|
||||
|
||||
keystoreOptions.value = hash;
|
||||
saveLocalKeystore();
|
||||
});
|
||||
|
||||
readCredentialButton.addEventListener("click", async () => {
|
||||
const password = keystorePassword.value;
|
||||
|
||||
if (!password) {
|
||||
alert(
|
||||
"Please, input password in order to read credential from Keystore."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentHash = keystoreOptions.value;
|
||||
|
||||
if (!currentHash) {
|
||||
alert(
|
||||
"Please, select hash of a key in order to read credential from Keystore."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const credential = await readCredential(currentHash, password);
|
||||
_renderCredential(currentHash, credential);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
registerEvents,
|
||||
onStatusChange: (value, category = "progress") => {
|
||||
status.className = category;
|
||||
status.innerText = value;
|
||||
},
|
||||
};
|
||||
}
|
9
examples/rln-identity/src/utils.js
Normal file
9
examples/rln-identity/src/utils.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { bytesToHex } from "@waku/utils/bytes";
|
||||
|
||||
export function randomNumber() {
|
||||
return Math.ceil(Math.random() * 1000);
|
||||
}
|
||||
|
||||
export function renderBytes(bytes) {
|
||||
return bytes ? bytesToHex(bytes) : "none";
|
||||
}
|
19
examples/rln-identity/webpack.config.js
Normal file
19
examples/rln-identity/webpack.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/index.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "build"),
|
||||
filename: "./index.js",
|
||||
},
|
||||
experiments: {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
mode: "development",
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: ["index.html", "favicon.ico", "favicon.png", "manifest.json"],
|
||||
}),
|
||||
],
|
||||
};
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title></title>
|
||||
<title>RLN Chat</title>
|
||||
<link rel="apple-touch-icon" href="./favicon.png" />
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
<link rel="icon" href="./favicon.ico" />
|
||||
@ -147,7 +147,7 @@
|
||||
<div class="container">
|
||||
<div class="status">
|
||||
<h3>
|
||||
<b>Waku Status:</b>
|
||||
<b>Status:</b>
|
||||
<span id="status" class="progress">Starting...</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
@ -15,11 +15,8 @@
|
||||
"protobufjs": "^7.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@metamask/types": "^1.1.0",
|
||||
"@types/node": "^20",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "13.5.6",
|
||||
"typescript": "^5",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user