localgreenchain/lib/privacy/anonymity.ts
Claude ccea9535d4
Add Tor integration and privacy features for anonymous plant sharing
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.
2025-11-16 12:32:59 +00:00

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'],
};
}