1import { randomBytes, createPrivateKey, createPublicKey, createHash } from 'crypto' 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) { 24 out += b32Charset[(val >> (bits - 5)) & 31] 28 if (bits > 0) out += b32Charset[(val << (5 - bits)) & 31] 32export const deriveOnionAddr = (pubkey: Buffer): string => { 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()) 45 return Buffer.concat([secKeyTag, h]) 48export const genTorOnionKeyIfMissing = ({secretName}: {secretName: string}) => { 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)} 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)}