-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a6c92b9
commit 9c8fa62
Showing
3 changed files
with
118 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters