diff --git a/common/TOR.ts b/common/TOR.ts index c9d427b..aa00027 100644 --- a/common/TOR.ts +++ b/common/TOR.ts @@ -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 => { - 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 = 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; +} \ No newline at end of file diff --git a/common/components/MainPage.scss b/common/components/MainPage.scss index 6726156..8703ff5 100644 --- a/common/components/MainPage.scss +++ b/common/components/MainPage.scss @@ -68,5 +68,17 @@ flex-direction: column; } } + &>.tor { + span { + display: flex; + flex-wrap: wrap; + b { + width: 50%; + } + a { + text-align: center; + } + } + } } } diff --git a/common/components/MainPage.tsx b/common/components/MainPage.tsx index bcd2e19..3a42d12 100644 --- a/common/components/MainPage.tsx +++ b/common/components/MainPage.tsx @@ -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
-
-

How many digits of PI to calculate?

- -
this.computePI()} class="button">Compute
+
+

Multi-Threading TOR Address

+ +
this.createVanityAddress()} class="button">Compute
-
-

Multi-Threading with 10 runners

- -
Compute
+
+

How many digits of PI to calculate?

+ +
this.computePI()} class="button">Compute
}) 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(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( + {result.address} + Pub: {result.public.b64} + Priv: {result.private.b64} + ) + parent.classList.remove("hidden"); + disposeThread(...threads); + } @standalone async computePI() { @@ -33,13 +54,4 @@ export class MainPage extends UIX.BaseComponent { parent.querySelector("section")!.prepend({pi}) parent.classList.remove("hidden"); } - - override async onDisplay() { - return; - const threads = await spawnThreads(new Path('../Worker.ts'), 10); - const calculations = threads.map(thread => thread.heavyCalculation(1,2)); - - const result = await Promise.any(calculations); - console.log(">>", result) - } } \ No newline at end of file