localgreenchain/lib/transparency/DigitalSignatures.ts
Claude 0fcc2763fe
Add comprehensive transparency system for LocalGreenChain
This commit introduces a complete transparency infrastructure including:

Core Transparency Modules:
- AuditLog: Immutable, cryptographically-linked audit trail for all actions
- EventStream: Real-time SSE streaming and webhook support
- TransparencyDashboard: Aggregated metrics and system health monitoring
- DigitalSignatures: Cryptographic verification for handoffs and certificates

API Endpoints:
- /api/transparency/dashboard - Full platform metrics
- /api/transparency/audit - Query and log audit entries
- /api/transparency/events - SSE stream and event history
- /api/transparency/webhooks - Webhook management
- /api/transparency/signatures - Digital signature operations
- /api/transparency/certificate/[plantId] - Plant authenticity certificates
- /api/transparency/export - Multi-format data export
- /api/transparency/report - Compliance reporting
- /api/transparency/health - System health checks

Features:
- Immutable audit logging with chain integrity verification
- Real-time event streaming via Server-Sent Events
- Webhook support with HMAC signature verification
- Digital signatures for transport handoffs and ownership transfers
- Certificate of Authenticity generation for plants
- Multi-format data export (JSON, CSV, summary)
- Public transparency portal at /transparency
- System health monitoring for all components

Documentation:
- Comprehensive TRANSPARENCY.md guide with API examples
2025-11-23 03:29:56 +00:00

617 lines
15 KiB
TypeScript

/**
* Digital Signatures System for LocalGreenChain
*
* Provides cryptographic verification for:
* - Transport handoffs
* - Plant ownership transfers
* - Data integrity verification
* - Multi-party attestations
*/
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
// Signature Types
export type SignatureType =
| 'OWNERSHIP_TRANSFER'
| 'TRANSPORT_HANDOFF'
| 'HARVEST_ATTESTATION'
| 'QUALITY_CERTIFICATION'
| 'ORIGIN_VERIFICATION'
| 'DATA_INTEGRITY';
export interface KeyPair {
publicKey: string;
privateKey: string;
algorithm: 'RSA' | 'ECDSA';
createdAt: string;
}
export interface SignatureIdentity {
id: string;
name: string;
publicKey: string;
type: 'GROWER' | 'TRANSPORTER' | 'CERTIFIER' | 'CONSUMER' | 'SYSTEM';
verified: boolean;
createdAt: string;
metadata?: Record<string, any>;
}
export interface DigitalSignature {
id: string;
type: SignatureType;
signerId: string;
signerPublicKey: string;
timestamp: string;
data: string; // JSON stringified data that was signed
signature: string; // Base64 encoded signature
algorithm: string;
resourceType: string;
resourceId: string;
metadata?: Record<string, any>;
}
export interface MultiSignature {
id: string;
type: SignatureType;
resourceType: string;
resourceId: string;
requiredSigners: string[];
signatures: DigitalSignature[];
threshold: number;
complete: boolean;
createdAt: string;
completedAt?: string;
expiresAt?: string;
}
export interface SignatureVerification {
valid: boolean;
signerId: string;
timestamp: string;
resourceId: string;
errors?: string[];
}
export interface CertificateOfAuthenticity {
id: string;
plantId: string;
plantName: string;
variety: string;
origin: {
grower: string;
location: string;
registeredAt: string;
};
lineage: {
generation: number;
parentId?: string;
childCount: number;
};
signatures: Array<{
type: SignatureType;
signer: string;
timestamp: string;
verified: boolean;
}>;
transportHistory: Array<{
from: string;
to: string;
date: string;
verified: boolean;
}>;
certifications: string[];
qrCode?: string;
generatedAt: string;
hash: string;
}
class SignatureManager {
private identities: Map<string, SignatureIdentity> = new Map();
private signatures: Map<string, DigitalSignature> = new Map();
private multiSignatures: Map<string, MultiSignature> = new Map();
private dataDir: string;
private dataFile: string;
constructor() {
this.dataDir = path.join(process.cwd(), 'data');
this.dataFile = path.join(this.dataDir, 'signatures.json');
this.load();
}
private load(): void {
try {
if (fs.existsSync(this.dataFile)) {
const data = JSON.parse(fs.readFileSync(this.dataFile, 'utf-8'));
if (data.identities) {
data.identities.forEach((id: SignatureIdentity) => {
this.identities.set(id.id, id);
});
}
if (data.signatures) {
data.signatures.forEach((sig: DigitalSignature) => {
this.signatures.set(sig.id, sig);
});
}
if (data.multiSignatures) {
data.multiSignatures.forEach((ms: MultiSignature) => {
this.multiSignatures.set(ms.id, ms);
});
}
console.log(`[SignatureManager] Loaded ${this.identities.size} identities, ${this.signatures.size} signatures`);
}
} catch (error) {
console.error('[SignatureManager] Error loading data:', error);
}
}
private save(): void {
try {
if (!fs.existsSync(this.dataDir)) {
fs.mkdirSync(this.dataDir, { recursive: true });
}
const data = {
identities: Array.from(this.identities.values()),
signatures: Array.from(this.signatures.values()),
multiSignatures: Array.from(this.multiSignatures.values()),
savedAt: new Date().toISOString()
};
fs.writeFileSync(this.dataFile, JSON.stringify(data, null, 2));
} catch (error) {
console.error('[SignatureManager] Error saving data:', error);
}
}
/**
* Generate a new key pair
*/
generateKeyPair(algorithm: 'RSA' | 'ECDSA' = 'ECDSA'): KeyPair {
if (algorithm === 'RSA') {
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
return {
publicKey,
privateKey,
algorithm: 'RSA',
createdAt: new Date().toISOString()
};
} else {
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
namedCurve: 'secp256k1',
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
return {
publicKey,
privateKey,
algorithm: 'ECDSA',
createdAt: new Date().toISOString()
};
}
}
/**
* Register a new identity
*/
registerIdentity(
name: string,
type: SignatureIdentity['type'],
publicKey: string,
metadata?: Record<string, any>
): SignatureIdentity {
const id = `id_${crypto.randomBytes(16).toString('hex')}`;
const identity: SignatureIdentity = {
id,
name,
publicKey,
type,
verified: false,
createdAt: new Date().toISOString(),
metadata
};
this.identities.set(id, identity);
this.save();
return identity;
}
/**
* Get identity by ID
*/
getIdentity(id: string): SignatureIdentity | undefined {
return this.identities.get(id);
}
/**
* Get all identities
*/
getAllIdentities(): SignatureIdentity[] {
return Array.from(this.identities.values());
}
/**
* Verify an identity
*/
verifyIdentity(id: string): boolean {
const identity = this.identities.get(id);
if (identity) {
identity.verified = true;
this.save();
return true;
}
return false;
}
/**
* Sign data
*/
sign(
type: SignatureType,
resourceType: string,
resourceId: string,
data: any,
privateKey: string,
signerId: string,
metadata?: Record<string, any>
): DigitalSignature {
const dataString = JSON.stringify(data);
// Create signature
const sign = crypto.createSign('SHA256');
sign.update(dataString);
const signature = sign.sign(privateKey, 'base64');
const id = `sig_${Date.now()}_${crypto.randomBytes(8).toString('hex')}`;
const identity = this.identities.get(signerId);
const digitalSignature: DigitalSignature = {
id,
type,
signerId,
signerPublicKey: identity?.publicKey || '',
timestamp: new Date().toISOString(),
data: dataString,
signature,
algorithm: 'SHA256',
resourceType,
resourceId,
metadata
};
this.signatures.set(id, digitalSignature);
this.save();
return digitalSignature;
}
/**
* Verify a signature
*/
verify(signatureId: string): SignatureVerification {
const sig = this.signatures.get(signatureId);
if (!sig) {
return {
valid: false,
signerId: '',
timestamp: '',
resourceId: '',
errors: ['Signature not found']
};
}
try {
const verify = crypto.createVerify('SHA256');
verify.update(sig.data);
const isValid = verify.verify(sig.signerPublicKey, sig.signature, 'base64');
return {
valid: isValid,
signerId: sig.signerId,
timestamp: sig.timestamp,
resourceId: sig.resourceId,
errors: isValid ? undefined : ['Signature verification failed']
};
} catch (error) {
return {
valid: false,
signerId: sig.signerId,
timestamp: sig.timestamp,
resourceId: sig.resourceId,
errors: [`Verification error: ${error}`]
};
}
}
/**
* Verify data with signature directly
*/
verifyData(data: any, signature: string, publicKey: string): boolean {
try {
const dataString = JSON.stringify(data);
const verify = crypto.createVerify('SHA256');
verify.update(dataString);
return verify.verify(publicKey, signature, 'base64');
} catch {
return false;
}
}
/**
* Create a multi-signature request
*/
createMultiSignature(
type: SignatureType,
resourceType: string,
resourceId: string,
requiredSigners: string[],
threshold?: number,
expiresIn?: number
): MultiSignature {
const id = `msig_${Date.now()}_${crypto.randomBytes(8).toString('hex')}`;
const multiSig: MultiSignature = {
id,
type,
resourceType,
resourceId,
requiredSigners,
signatures: [],
threshold: threshold || requiredSigners.length,
complete: false,
createdAt: new Date().toISOString(),
expiresAt: expiresIn ? new Date(Date.now() + expiresIn).toISOString() : undefined
};
this.multiSignatures.set(id, multiSig);
this.save();
return multiSig;
}
/**
* Add a signature to a multi-signature request
*/
addToMultiSignature(
multiSigId: string,
signature: DigitalSignature
): MultiSignature | null {
const multiSig = this.multiSignatures.get(multiSigId);
if (!multiSig) return null;
// Check if signer is required
if (!multiSig.requiredSigners.includes(signature.signerId)) {
return null;
}
// Check if already signed
if (multiSig.signatures.some(s => s.signerId === signature.signerId)) {
return multiSig;
}
// Check expiration
if (multiSig.expiresAt && new Date(multiSig.expiresAt) < new Date()) {
return null;
}
multiSig.signatures.push(signature);
// Check if complete
if (multiSig.signatures.length >= multiSig.threshold) {
multiSig.complete = true;
multiSig.completedAt = new Date().toISOString();
}
this.save();
return multiSig;
}
/**
* Get multi-signature status
*/
getMultiSignature(id: string): MultiSignature | undefined {
return this.multiSignatures.get(id);
}
/**
* Get signatures for a resource
*/
getSignaturesForResource(resourceType: string, resourceId: string): DigitalSignature[] {
return Array.from(this.signatures.values()).filter(
s => s.resourceType === resourceType && s.resourceId === resourceId
);
}
/**
* Generate a Certificate of Authenticity
*/
async generateCertificate(plantId: string): Promise<CertificateOfAuthenticity | null> {
try {
// Load plant data
const chainFile = path.join(this.dataDir, 'plantchain.json');
if (!fs.existsSync(chainFile)) {
return null;
}
const chainData = JSON.parse(fs.readFileSync(chainFile, 'utf-8'));
const chain = chainData.chain || [];
// Find plant
const plantBlock = chain.find((b: any) => b.plant?.id === plantId);
if (!plantBlock) {
return null;
}
const plant = plantBlock.plant;
// Get signatures for this plant
const plantSignatures = this.getSignaturesForResource('plant', plantId);
// Build certificate
const certificate: CertificateOfAuthenticity = {
id: `cert_${crypto.randomBytes(16).toString('hex')}`,
plantId,
plantName: plant.name || 'Unknown',
variety: plant.variety || 'Unknown',
origin: {
grower: plant.ownerId || 'Unknown',
location: plant.location?.region || 'Unknown',
registeredAt: plantBlock.timestamp
},
lineage: {
generation: plant.generation || 1,
parentId: plant.parentId,
childCount: plant.childPlants?.length || 0
},
signatures: plantSignatures.map(sig => ({
type: sig.type,
signer: sig.signerId,
timestamp: sig.timestamp,
verified: this.verify(sig.id).valid
})),
transportHistory: [],
certifications: [],
generatedAt: new Date().toISOString(),
hash: ''
};
// Calculate certificate hash
certificate.hash = crypto
.createHash('sha256')
.update(JSON.stringify({
plantId: certificate.plantId,
origin: certificate.origin,
lineage: certificate.lineage,
signatures: certificate.signatures
}))
.digest('hex');
return certificate;
} catch (error) {
console.error('[SignatureManager] Error generating certificate:', error);
return null;
}
}
/**
* Verify a certificate
*/
verifyCertificate(certificate: CertificateOfAuthenticity): { valid: boolean; errors: string[] } {
const errors: string[] = [];
// Verify hash
const expectedHash = crypto
.createHash('sha256')
.update(JSON.stringify({
plantId: certificate.plantId,
origin: certificate.origin,
lineage: certificate.lineage,
signatures: certificate.signatures
}))
.digest('hex');
if (certificate.hash !== expectedHash) {
errors.push('Certificate hash mismatch - data may have been tampered');
}
// Verify each signature
for (const sig of certificate.signatures) {
if (!sig.verified) {
errors.push(`Signature from ${sig.signer} is not verified`);
}
}
return {
valid: errors.length === 0,
errors
};
}
/**
* Create a transport handoff signature
*/
createTransportHandoff(
plantId: string,
fromParty: string,
toParty: string,
location: { lat: number; lng: number },
fromPrivateKey: string
): DigitalSignature {
const handoffData = {
plantId,
fromParty,
toParty,
location,
timestamp: new Date().toISOString(),
type: 'TRANSPORT_HANDOFF'
};
return this.sign(
'TRANSPORT_HANDOFF',
'transport',
plantId,
handoffData,
fromPrivateKey,
fromParty,
{ toParty, location }
);
}
/**
* Get statistics
*/
getStats(): {
totalIdentities: number;
verifiedIdentities: number;
totalSignatures: number;
signaturesByType: Record<string, number>;
pendingMultiSigs: number;
completedMultiSigs: number;
} {
const signaturesByType: Record<string, number> = {};
for (const sig of this.signatures.values()) {
signaturesByType[sig.type] = (signaturesByType[sig.type] || 0) + 1;
}
const multiSigArray = Array.from(this.multiSignatures.values());
return {
totalIdentities: this.identities.size,
verifiedIdentities: Array.from(this.identities.values()).filter(i => i.verified).length,
totalSignatures: this.signatures.size,
signaturesByType,
pendingMultiSigs: multiSigArray.filter(ms => !ms.complete).length,
completedMultiSigs: multiSigArray.filter(ms => ms.complete).length
};
}
}
// Singleton instance
let signatureManagerInstance: SignatureManager | null = null;
export function getSignatureManager(): SignatureManager {
if (!signatureManagerInstance) {
signatureManagerInstance = new SignatureManager();
}
return signatureManagerInstance;
}
export default SignatureManager;