🌳
pt0/deployF/servicesF/torF/genTorOnionKeyAI.mts
1import { randomBytes, createPrivateKey, createPublicKey, createHash } from 'crypto'
2import fs from 'fs'
7const pkcs8Ed25519Prefix = Buffer.from('302e020100300506032b657004220420', 'hex')
8const secKeyTag = Buffer.concat([Buffer.from('== ed25519v1-secret: type0 =='), Buffer.alloc(3)])
9const b32Charset = 'abcdefghijklmnopqrstuvwxyz234567'
11export const deriveEd25519Pubkey = (seed: Buffer): Buffer => {
12 const pkcs8 = Buffer.concat([pkcs8Ed25519Prefix, seed])
13 const priv = createPrivateKey({key: pkcs8, format: 'der', type: 'pkcs8'})
14 const spki = createPublicKey(priv).export({type: 'spki', format: 'der'}) as Buffer
15 return spki.subarray(12)
18export const base32LowerNoPad = (buf: Buffer): string => {
19 let bits = 0, val = 0, out = ''
20 for (const b of buf) {
21 val = (val << 8) | b
22 bits += 8
23 while (bits >= 5) {
24 out += b32Charset[(val >> (bits - 5)) & 31]
25 bits -= 5
26 }
27 }
28 if (bits > 0) out += b32Charset[(val << (5 - bits)) & 31]
29 return out
32export const deriveOnionAddr = (pubkey: Buffer): string => {
33 const version = 3
34 const checksum = createHash('sha3-256')
35 .update(Buffer.concat([Buffer.from('.onion checksum'), pubkey, Buffer.from([version])]))
36 .digest().subarray(0, 2)
37 return base32LowerNoPad(Buffer.concat([pubkey, checksum, Buffer.from([version])])) + '.onion'
40export const buildTorSecKeyFile = (seed: Buffer): Buffer => {
41 const h = Buffer.from(createHash('sha512').update(seed).digest())
42 h[0] &= 248
43 h[31] &= 127
44 h[31] |= 64
45 return Buffer.concat([secKeyTag, h])
48export const genTorOnionKeyIfMissing = ({secretName}: {secretName: string}) => {
49 const secPath = pathDownJoin(secretsDir, `${secretName}.json`)
50 if (fs.existsSync(secPath)) {
51 const {seed} = JSON.parse(fs.readFileSync(secPath, 'utf-8'))
52 const seedBuf = Buffer.from(seed, 'hex')
53 const pubkey = deriveEd25519Pubkey(seedBuf)
54 return {seed: seedBuf, onionAddr: deriveOnionAddr(pubkey), secKeyFileBytes: buildTorSecKeyFile(seedBuf)}
55 }
56 assertExists(secretsDir, {secretName})
57 const seedBuf = randomBytes(32)
58 const pubkey = deriveEd25519Pubkey(seedBuf)
59 const onionAddr = deriveOnionAddr(pubkey)
60 fs.writeFileSync(secPath, JSON.stringify({seed: seedBuf.toString('hex'), onionAddr}))
61 return {seed: seedBuf, onionAddr, secKeyFileBytes: buildTorSecKeyFile(seedBuf)}