/** * Persistent client DHT identity for stable peerId across reconnects. * Stored at ~/.config/peardata/identity.json (mode 0600). * * Uses fs/path/os/crypto via package.json import maps → bare-* under Bare/Pear. */ import fs from 'fs' import path from 'path' import os from 'os' import crypto from 'crypto' import DHT from 'hyperdht' import b4a from 'b4a' const IDENTITY_VERSION = 1 function envGet(name) { try { return typeof process !== 'undefined' ? process.env?.[name] : undefined } catch { return undefined } } export function getIdentityPath() { const home = envGet('PEARDATA_HOME') || envGet('HOME') || envGet('USERPROFILE') || (typeof os.homedir === 'function' ? os.homedir() : '') || '' return path.join(home, '.config', 'peardata', 'identity.json') } export function loadOrCreateClientIdentity() { const filePath = getIdentityPath() let seedHex = null try { if (fs.existsSync(filePath)) { const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) if (raw?.seedHex && /^[0-9a-fA-F]{64}$/.test(raw.seedHex)) { seedHex = String(raw.seedHex).toLowerCase() } } } catch { // regenerate } if (!seedHex) { seedHex = b4a.toString(crypto.randomBytes(32), 'hex') try { const dir = path.dirname(filePath) if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 }) fs.writeFileSync( filePath, JSON.stringify( { version: IDENTITY_VERSION, seedHex, createdAt: new Date().toISOString(), }, null, 2 ), { mode: 0o600 } ) try { fs.chmodSync(filePath, 0o600) } catch { // ignore } } catch { // In-memory only if FS unavailable } } const seed = b4a.from(seedHex, 'hex') const keyPair = DHT.keyPair(seed) const publicKeyHex = b4a.toString(keyPair.publicKey, 'hex') return { seed, keyPair, publicKeyHex, seedHex } } let cached = null export function getClientIdentity() { if (!cached) cached = loadOrCreateClientIdentity() return cached }