Skip to content

Commit

Permalink
Add TOR
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasstrehle committed Sep 21, 2023
1 parent a6c92b9 commit 9c8fa62
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 69 deletions.
127 changes: 76 additions & 51 deletions common/TOR.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,95 @@
// check out https://github.com/paulmillr/ed25519-keygen/blob/main/src/tor.ts
const ed = await import("https://unpkg.com/@noble/ed25519");
const ed = await import("https://unpkg.com/@noble/ed25519@2.0.0/index.js");
const base32 = await import("https://cdn.jsdelivr.net/npm/hi-base32@0.5.1/+esm");
await import("https://cdn.jsdelivr.net/npm/js-sha3@0.9.2/src/sha3.min.js");

declare const sha3_256: any;
type KeyPair = {
private: Uint8Array,
public: Uint8Array,
private: PrivateKey,
public: PublicKey,
};
class PublicKey {
constructor(public readonly value: Uint8Array) {}
toString() {
return btoa("== ed25519v1-public: type0 ==\x00\x00\x00".concat(String.fromCharCode.apply(null, [...this.value])));
}
equals(other: PublicKey) {
return this.value.join() === other.value.join();
}
}
class PrivateKey {
constructor(public readonly value: Uint8Array) {}
toString() {
return btoa("== ed25519v1-secret: type0 ==\x00\x00\x00".concat(String.fromCharCode.apply(null, [...this.value])));
}
}

const generateKeys = async (privateKey = ed.utils.randomPrivateKey()): Promise<KeyPair> => {
const publicKey = await ed.getPublicKeyAsync(privateKey);
return {
private: privateKey,
public: publicKey
};
const publicKey = await ed.getPublicKeyAsync(privateKey);
return {
private: new PrivateKey(privateKey),
public: new PublicKey(publicKey)
};
}

const getPublicKey = (address: string): Uint8Array => {
if (!/\.onion$/i.test(address) || address.length != 56 + 6)
throw new Error("Invalid length");
const base32Encoded = address.substr(0, address.length - 6).toUpperCase();
const decoded = base32.decode.asBytes(base32Encoded);
const version = decoded.at(-1);
if (version !== 0x03)
throw new Error("Invalid version");
const getPublicKey = (address: string) => {
if (!/\.onion$/i.test(address) || address.length != 56 + 6)
throw new Error("Invalid length");
const base32Encoded = address.substring(0, address.length - 6).toUpperCase();
const decoded = base32.decode.asBytes(base32Encoded);
const version = decoded.at(-1);
if (version !== 0x03)
throw new Error("Invalid version");

const checksum = decoded.slice(decoded.length - 3, decoded.length - 1);
const publicKey = decoded.slice(0, decoded.length - 3);
const checksum = decoded.slice(decoded.length - 3, decoded.length - 1);
const publicKey = decoded.slice(0, decoded.length - 3);

const encoder = new TextEncoder();
const hash = sha3_256.create();
hash.update(encoder.encode(".onion checksum"));
hash.update(publicKey);
hash.update(new Uint8Array([0x03]));
const encoder = new TextEncoder();
const hash = sha3_256.create();
hash.update(encoder.encode(".onion checksum"));
hash.update(publicKey);
hash.update(new Uint8Array([0x03]));

const _checksum = hash.digest().slice(0, 2);
if (checksum.join() !== _checksum.join())
throw new Error("Checksum is invalid");
return publicKey;
const _checksum = hash.digest().slice(0, 2);
if (checksum.join() !== _checksum.join())
throw new Error("Checksum is invalid");
return new PublicKey(new Uint8Array(publicKey));
}

const generateOnionV3 = async (keys: KeyPair | Promise<KeyPair> = generateKeys()) => {
keys = await keys;
const encoder = new TextEncoder();
const hash = sha3_256.create();
const version = new Uint8Array([0x03]);
hash.update(encoder.encode(".onion checksum"));
hash.update(keys.public);
hash.update(version);
keys = await keys;

const publicKey = keys.public.value;

const checksum = hash.digest().slice(0, 2);

const decoded = new Uint8Array([...keys.public, ...checksum, ...version]);
const address = base32.encode(Array.from(decoded)).toLowerCase().concat(".onion");
const _publicKey = getPublicKey(address);
if (keys.public.join() !== _publicKey.join())
throw new Error("Public key is invalid");
return {
address,
...keys
}
const encoder = new TextEncoder();
const hash = sha3_256.create();
const version = new Uint8Array([0x03]);
hash.update(encoder.encode(".onion checksum"));
hash.update(publicKey);
hash.update(version);

const checksum = hash.digest().slice(0, 2);

const decoded = new Uint8Array([...publicKey, ...checksum, ...version]);
const address = base32.encode(Array.from(decoded)).toLowerCase().concat(".onion");
const _publicKey = getPublicKey(address);
if (!keys.public.equals(_publicKey))
throw new Error("Public key is invalid");
return {
address,
public: {
raw: keys.public.value,
b64: keys.public.toString()
},
private: {
raw: keys.private.value,
b64: keys.private.toString()
}
}
}

export const generateVanityAddress = async (prefix?: string) => {
let onion = await generateOnionV3();
while (prefix && !onion.address.startsWith(prefix))
onion = await generateOnionV3();
return onion;
}
let onion = await generateOnionV3();
while (prefix && !onion.address.startsWith(prefix))
onion = await generateOnionV3();
return onion;
}
12 changes: 12 additions & 0 deletions common/components/MainPage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,17 @@
flex-direction: column;
}
}
&>.tor {
span {
display: flex;
flex-wrap: wrap;
b {
width: 50%;
}
a {
text-align: center;
}
}
}
}
}
48 changes: 30 additions & 18 deletions common/components/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
import { Path } from "uix/utils/path.ts";
import { UIX } from "uix";
import { spawnThreads, spawnThread } from "unyt_core/threads/threads.ts";
import { spawnThreads, spawnThread, disposeThread } from "unyt_core/threads/threads.ts";

@UIX.template(function(this: MainPage) {
return <div>
<div>
<h2>How many digits of PI to calculate?</h2>
<input id="inputPiDigits" type={"number"} placeholder={""}/>
<div onclick={() => this.computePI()} class="button">Compute</div>
<div class="tor">
<h2>Multi-Threading TOR Address</h2>
<input id="torAddress" maxlength={3} type={"text"} placeholder={"Prefix of vanity address"}/>
<div onclick={() => this.createVanityAddress()} class="button">Compute</div>
<section class="results"></section>
</div>
<div>
<h2>Multi-Threading with 10 runners</h2>
<input id="input" type={"number"} placeholder={""}/>
<div class="button">Compute</div>
<div class="pi">
<h2>How many digits of PI to calculate?</h2>
<input id="inputPiDigits" type={"number"} placeholder={"Input number"}/>
<div onclick={() => this.computePI()} class="button">Compute</div>
<section class="results"></section>
</div>
</div>
})
export class MainPage extends UIX.BaseComponent {
@id declare inputPiDigits: HTMLInputElement;
@id declare torAddress: HTMLInputElement;

@standalone
async createVanityAddress() {
const parent = this.torAddress.parentElement!;
if (parent.classList.contains("hidden"))
return;
parent.classList.add("hidden");

const threads = await spawnThreads<typeof import('../TOR.ts')>(new Path('../TOR.ts'), 10);
const calculations = threads.map(thread => thread.generateVanityAddress(this.torAddress.value));
const result = await Promise.any(calculations);
console.log("Found address", result);
parent.querySelector("section")!.prepend(<span>
<a>{result.address}</a>
<b>Pub: {result.public.b64}</b>
<b>Priv: {result.private.b64}</b>
</span>)
parent.classList.remove("hidden");
disposeThread(...threads);
}

@standalone
async computePI() {
Expand All @@ -33,13 +54,4 @@ export class MainPage extends UIX.BaseComponent {
parent.querySelector("section")!.prepend(<span>{pi}</span>)
parent.classList.remove("hidden");
}

override async onDisplay() {
return;
const threads = await spawnThreads<typeof import('../Worker.ts')>(new Path('../Worker.ts'), 10);
const calculations = threads.map(thread => thread.heavyCalculation(1,2));

const result = await Promise.any(calculations);
console.log(">>", result)
}
}

0 comments on commit 9c8fa62

Please sign in to comment.