- 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
221 lines
6.5 KiB
TypeScript
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'],
|
|
};
|
|
}
|