Implements comprehensive privacy and anonymity features including Tor hidden service support, location obfuscation, and anonymous registration. Privacy Features: - Anonymous plant registration with zero personal information - Location privacy levels: exact, fuzzy, city, country, hidden - Pseudonymous identities and wallet addresses - Privacy settings component with real-time Tor status - Encrypted anonymous contact generation Tor Integration: - SOCKS proxy support for Tor connections - Hidden service (.onion) configuration - Tor connection detection and status API - Docker Compose setup for easy Tor deployment - Automatic privacy warnings when not using Tor Location Obfuscation: - Fuzzy location: ±1-5km random offset - City level: ~10km grid - Country level: ~100km grid - Hidden: complete location privacy - Haversine-based distance calculations preserved Anonymous Registration: - /plants/register-anonymous endpoint - Privacy-first UI with Tor status banner - Anonymous IDs and wallet addresses - Optional pseudonym support - Encryption key support for enhanced security Infrastructure: - Tor service integration (lib/services/tor.ts) - Privacy utilities (lib/privacy/anonymity.ts) - PrivacySettings React component - Tor status API endpoint - Docker and docker-compose configurations - Example Tor configuration (torrc.example) Documentation: - Comprehensive TOR_SETUP.md guide - Installation instructions for Linux/macOS/Windows - Privacy best practices - Troubleshooting guide - Security considerations - Updated README with Tor features Dependencies: - Added socks-proxy-agent for Tor proxy support This enables: - Privacy-conscious growers to share anonymously - Protection of exact home locations - Censorship-resistant plant sharing - Community building without identity disclosure - Compliance with privacy regulations All privacy features are optional and configurable. Users can choose their desired privacy level.
221 lines
6.3 KiB
TypeScript
221 lines
6.3 KiB
TypeScript
import crypto from 'crypto';
|
|
|
|
/**
|
|
* Privacy and Anonymity Utilities for LocalGreenChain
|
|
* Provides tools for anonymous plant tracking while maintaining blockchain integrity
|
|
*/
|
|
|
|
export interface PrivacySettings {
|
|
anonymousMode: boolean;
|
|
locationPrivacy: 'exact' | 'fuzzy' | 'city' | 'country' | 'hidden';
|
|
identityPrivacy: 'real' | 'pseudonym' | 'anonymous';
|
|
sharePlantDetails: boolean;
|
|
}
|
|
|
|
export interface FuzzyLocation {
|
|
latitude: number;
|
|
longitude: number;
|
|
accuracy: number; // radius in km
|
|
displayName: string;
|
|
}
|
|
|
|
/**
|
|
* Generate anonymous user ID using cryptographic hash
|
|
*/
|
|
export function generateAnonymousId(): string {
|
|
const randomBytes = crypto.randomBytes(32);
|
|
return 'anon_' + crypto.createHash('sha256').update(randomBytes).digest('hex').substring(0, 16);
|
|
}
|
|
|
|
/**
|
|
* Generate pseudonymous wallet address
|
|
*/
|
|
export function generateWalletAddress(): string {
|
|
const randomBytes = crypto.randomBytes(20);
|
|
return '0x' + randomBytes.toString('hex');
|
|
}
|
|
|
|
/**
|
|
* Obfuscate location based on privacy level
|
|
* This prevents exact home address tracking while allowing geographic discovery
|
|
*/
|
|
export function obfuscateLocation(
|
|
latitude: number,
|
|
longitude: number,
|
|
privacyLevel: 'exact' | 'fuzzy' | 'city' | 'country' | 'hidden'
|
|
): FuzzyLocation {
|
|
switch (privacyLevel) {
|
|
case 'exact':
|
|
return {
|
|
latitude,
|
|
longitude,
|
|
accuracy: 0.1, // ~100m
|
|
displayName: 'Exact location',
|
|
};
|
|
|
|
case 'fuzzy':
|
|
// Add random offset within 1-5km radius
|
|
const fuzzRadius = 1 + Math.random() * 4; // 1-5 km
|
|
const angle = Math.random() * 2 * Math.PI;
|
|
const latOffset = (fuzzRadius / 111) * Math.cos(angle); // 111 km per degree
|
|
const lonOffset = (fuzzRadius / (111 * Math.cos(latitude * Math.PI / 180))) * Math.sin(angle);
|
|
|
|
return {
|
|
latitude: latitude + latOffset,
|
|
longitude: longitude + lonOffset,
|
|
accuracy: fuzzRadius,
|
|
displayName: `Within ${Math.round(fuzzRadius)} km`,
|
|
};
|
|
|
|
case 'city':
|
|
// Round to ~10km grid (0.1 degree ≈ 11km)
|
|
return {
|
|
latitude: Math.round(latitude * 10) / 10,
|
|
longitude: Math.round(longitude * 10) / 10,
|
|
accuracy: 10,
|
|
displayName: 'City area',
|
|
};
|
|
|
|
case 'country':
|
|
// Round to ~100km grid (1 degree ≈ 111km)
|
|
return {
|
|
latitude: Math.round(latitude),
|
|
longitude: Math.round(longitude),
|
|
accuracy: 100,
|
|
displayName: 'Country/Region',
|
|
};
|
|
|
|
case 'hidden':
|
|
return {
|
|
latitude: 0,
|
|
longitude: 0,
|
|
accuracy: 999999,
|
|
displayName: 'Location hidden',
|
|
};
|
|
|
|
default:
|
|
return obfuscateLocation(latitude, longitude, 'fuzzy');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate anonymous plant name
|
|
*/
|
|
export function generateAnonymousPlantName(plantType: string, generation: number): string {
|
|
const hash = crypto.randomBytes(4).toString('hex');
|
|
return `${plantType}-Gen${generation}-${hash}`;
|
|
}
|
|
|
|
/**
|
|
* Encrypt sensitive data for storage
|
|
*/
|
|
export function encryptData(data: string, key: string): string {
|
|
const algorithm = 'aes-256-cbc';
|
|
const keyHash = crypto.createHash('sha256').update(key).digest();
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const cipher = crypto.createCipheriv(algorithm, keyHash, iv);
|
|
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
|
|
return iv.toString('hex') + ':' + encrypted;
|
|
}
|
|
|
|
/**
|
|
* Decrypt sensitive data
|
|
*/
|
|
export function decryptData(encryptedData: string, key: string): string {
|
|
const algorithm = 'aes-256-cbc';
|
|
const keyHash = crypto.createHash('sha256').update(key).digest();
|
|
|
|
const parts = encryptedData.split(':');
|
|
const iv = Buffer.from(parts[0], 'hex');
|
|
const encrypted = parts[1];
|
|
|
|
const decipher = crypto.createDecipheriv(algorithm, keyHash, iv);
|
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return decrypted;
|
|
}
|
|
|
|
/**
|
|
* Generate Tor-friendly onion address from plant ID
|
|
* This creates a deterministic onion-style identifier
|
|
*/
|
|
export function generateOnionIdentifier(plantId: string): string {
|
|
const hash = crypto.createHash('sha256').update(plantId).digest('hex');
|
|
return hash.substring(0, 16) + '.onion';
|
|
}
|
|
|
|
/**
|
|
* Create anonymous contact method
|
|
*/
|
|
export function createAnonymousContact(realEmail: string, privateKey: string): string {
|
|
// Hash email with private key to create anonymous identifier
|
|
const hash = crypto.createHash('sha256')
|
|
.update(realEmail + privateKey)
|
|
.digest('hex');
|
|
return `anon-${hash.substring(0, 12)}@localgreenchain.onion`;
|
|
}
|
|
|
|
/**
|
|
* Validate Tor connection
|
|
*/
|
|
export async function isTorConnection(req: any): Promise<boolean> {
|
|
// Check if request is coming through Tor
|
|
const forwardedFor = req.headers['x-forwarded-for'];
|
|
const realIp = req.headers['x-real-ip'];
|
|
|
|
// Tor exit nodes typically set specific headers
|
|
// This is a simplified check
|
|
return (
|
|
req.headers['x-tor-connection'] === 'true' ||
|
|
req.socket?.remoteAddress?.includes('127.0.0.1') // Tor proxy
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Generate privacy report for plant
|
|
*/
|
|
export interface PrivacyReport {
|
|
locationPrivacy: string;
|
|
identityPrivacy: string;
|
|
dataEncryption: boolean;
|
|
torEnabled: boolean;
|
|
riskLevel: 'low' | 'medium' | 'high';
|
|
recommendations: string[];
|
|
}
|
|
|
|
export function generatePrivacyReport(settings: PrivacySettings): PrivacyReport {
|
|
const recommendations: string[] = [];
|
|
let riskLevel: 'low' | 'medium' | 'high' = 'low';
|
|
|
|
if (settings.locationPrivacy === 'exact') {
|
|
recommendations.push('Consider using fuzzy location to protect your privacy');
|
|
riskLevel = 'high';
|
|
}
|
|
|
|
if (settings.identityPrivacy === 'real') {
|
|
recommendations.push('Using real identity may compromise anonymity');
|
|
if (riskLevel === 'low') riskLevel = 'medium';
|
|
}
|
|
|
|
if (settings.sharePlantDetails && !settings.anonymousMode) {
|
|
recommendations.push('Sharing detailed plant info without anonymous mode enabled');
|
|
}
|
|
|
|
if (settings.anonymousMode && settings.locationPrivacy !== 'hidden') {
|
|
riskLevel = 'low';
|
|
recommendations.push('Good privacy settings enabled');
|
|
}
|
|
|
|
return {
|
|
locationPrivacy: settings.locationPrivacy,
|
|
identityPrivacy: settings.identityPrivacy,
|
|
dataEncryption: settings.anonymousMode,
|
|
torEnabled: false, // Will be detected at runtime
|
|
riskLevel,
|
|
recommendations: recommendations.length > 0 ? recommendations : ['Privacy settings are optimal'],
|
|
};
|
|
}
|