localgreenchain/lib/privacy/anonymity.ts
Claude 507df5912f
Deploy GrowerAdvisoryAgent (Agent 10) and fix type errors
- Add GrowerAdvisoryAgent test file
- Fix PlantChain constructor initialization order (plantIndex before genesis block)
- Fix blockchain.getChain() calls to use blockchain.chain property
- Add PropagationType export to blockchain types
- Fix SoilComposition.type property references (was soilType)
- Fix ClimateConditions.temperatureDay property references (was avgTemperature)
- Fix ClimateConditions.humidityAverage property references (was avgHumidity)
- Fix LightingConditions.naturalLight.hoursPerDay nested access
- Add 'critical' severity to QualityReport issues
- Add 'sqm' unit to PlantingRecommendation.quantityUnit
- Fix GrowerAdvisoryAgent growthMetrics property access
- Update TypeScript to v5 for react-hook-form compatibility
- Enable downlevelIteration in tsconfig for Map iteration
- Fix crypto Buffer type issues in anonymity.ts
- Fix zones.tsx status type comparison
- Fix next.config.js images.domains filter
- Rename [[...slug]].tsx to [...slug].tsx to resolve routing conflict
2025-11-23 00:44:58 +00:00

221 lines
6.5 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(new Uint8Array(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 = new Uint8Array(crypto.createHash('sha256').update(key).digest());
const iv = new Uint8Array(crypto.randomBytes(16));
const cipher = crypto.createCipheriv(algorithm, keyHash as any, iv as any);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return Buffer.from(iv).toString('hex') + ':' + encrypted;
}
/**
* Decrypt sensitive data
*/
export function decryptData(encryptedData: string, key: string): string {
const algorithm = 'aes-256-cbc';
const keyHash = new Uint8Array(crypto.createHash('sha256').update(key).digest());
const parts = encryptedData.split(':');
const iv = new Uint8Array(Buffer.from(parts[0], 'hex'));
const encrypted = parts[1];
const decipher = crypto.createDecipheriv(algorithm, keyHash as any, iv as any);
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'],
};
}