- Added a detailed implementation status report (impl.md). - Added the `@noble/hashes` dependency for Blake3 hashing. - Updated package.json and pnpm-lock.yaml accordingly. - Created a new component for Blake3 hashing demo.
169 lines
6.0 KiB
TypeScript
169 lines
6.0 KiB
TypeScript
/**
|
|
* Content Processing Service
|
|
* Implements the content processing pipeline as described in the specs
|
|
* Uses Blake hashing for content integrity and encryption key generation
|
|
*/
|
|
|
|
import { cryptoService } from './crypto.service';
|
|
import { ipfsService } from './ipfs.service';
|
|
|
|
/**
|
|
* Interface for processed content metadata
|
|
*/
|
|
interface ProcessedContentMetadata {
|
|
originalFilename: string;
|
|
normalizedFilename: string;
|
|
blakeHash: string;
|
|
ipfsCid: string;
|
|
combinedKey: string;
|
|
contentType: string;
|
|
size: number;
|
|
timestamp: number;
|
|
}
|
|
|
|
/**
|
|
* Service for processing content files
|
|
* Implements the content processing pipeline:
|
|
* 1. File discovery
|
|
* 2. Filename normalization
|
|
* 3. Original content hashing (Blake)
|
|
* 4. Content encryption
|
|
* 5. Encrypted content upload to IPFS
|
|
*/
|
|
class ContentProcessorService {
|
|
/**
|
|
* Process a file through the content processing pipeline
|
|
* @param file - File to process
|
|
* @returns Promise<ProcessedContentMetadata> - Metadata of the processed content
|
|
*/
|
|
async processFile(file: File): Promise<ProcessedContentMetadata> {
|
|
try {
|
|
// Step 1: Get file information
|
|
const originalFilename = file.name;
|
|
|
|
// Step 2: Normalize filename (lowercase and snake_case)
|
|
const normalizedFilename = this.normalizeFilename(originalFilename);
|
|
|
|
// Step 3: Hash the original content using Blake3
|
|
const blakeHash = await cryptoService.hashFileBlake3AsHex(file);
|
|
|
|
// Step 4: Encrypt the content using the Blake hash as the key
|
|
// Note: This is a placeholder for actual encryption implementation
|
|
const encryptedContent = await this.encryptContent(file, blakeHash);
|
|
|
|
// Step 5: Upload the encrypted content to IPFS
|
|
const ipfsCid = await ipfsService.uploadContent(encryptedContent);
|
|
|
|
// Create the combined key (Blake hash + IPFS CID)
|
|
const combinedKey = cryptoService.combineBlakeHashAndIpfsCid(blakeHash, ipfsCid);
|
|
|
|
// Return the metadata
|
|
return {
|
|
originalFilename,
|
|
normalizedFilename,
|
|
blakeHash,
|
|
ipfsCid,
|
|
combinedKey,
|
|
contentType: file.type,
|
|
size: file.size,
|
|
timestamp: Date.now()
|
|
};
|
|
} catch (error) {
|
|
console.error('Error processing file:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Normalize a filename to lowercase and snake_case
|
|
* @param filename - Original filename
|
|
* @returns Normalized filename
|
|
*/
|
|
normalizeFilename(filename: string): string {
|
|
// Remove file extension
|
|
const parts = filename.split('.');
|
|
const extension = parts.pop() || '';
|
|
let name = parts.join('.');
|
|
|
|
// Convert to lowercase
|
|
name = name.toLowerCase();
|
|
|
|
// Replace spaces and special characters with underscores
|
|
name = name.replace(/[^a-z0-9]/g, '_');
|
|
|
|
// Replace multiple underscores with a single one
|
|
name = name.replace(/_+/g, '_');
|
|
|
|
// Remove leading and trailing underscores
|
|
name = name.replace(/^_+|_+$/g, '');
|
|
|
|
// Add extension back if it exists
|
|
if (extension) {
|
|
name = `${name}.${extension.toLowerCase()}`;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Encrypt content using the Blake hash as the key
|
|
* This is a placeholder for actual encryption implementation
|
|
* @param file - File to encrypt
|
|
* @param key - Encryption key (Blake hash)
|
|
* @returns Promise<string> - Encrypted content as string
|
|
*/
|
|
async encryptContent(file: File, key: string): Promise<string> {
|
|
// TODO: Implement actual encryption
|
|
// For now, we'll just return a mock encrypted content
|
|
// This should be replaced with actual encryption using the Blake hash as the key
|
|
|
|
// Convert the file to a string
|
|
const arrayBuffer = await file.arrayBuffer();
|
|
const contentBytes = new Uint8Array(arrayBuffer);
|
|
|
|
// Mock encryption by XORing with the key bytes
|
|
// This is NOT secure and is only for demonstration purposes
|
|
const keyBytes = cryptoService.hexToBytes(key);
|
|
const encryptedBytes = new Uint8Array(contentBytes.length);
|
|
|
|
for (let i = 0; i < contentBytes.length; i++) {
|
|
encryptedBytes[i] = contentBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
}
|
|
|
|
// Convert to base64 for storage
|
|
return btoa(String.fromCharCode.apply(null, Array.from(encryptedBytes)));
|
|
}
|
|
|
|
/**
|
|
* Decrypt content using the Blake hash as the key
|
|
* This is a placeholder for actual decryption implementation
|
|
* @param encryptedContent - Encrypted content as string
|
|
* @param key - Decryption key (Blake hash)
|
|
* @returns Promise<Uint8Array> - Decrypted content as Uint8Array
|
|
*/
|
|
async decryptContent(encryptedContent: string, key: string): Promise<Uint8Array> {
|
|
// TODO: Implement actual decryption
|
|
// For now, we'll just return a mock decrypted content
|
|
// This should be replaced with actual decryption using the Blake hash as the key
|
|
|
|
// Convert the base64 string to bytes
|
|
const encryptedBytes = new Uint8Array(
|
|
atob(encryptedContent).split('').map(c => c.charCodeAt(0))
|
|
);
|
|
|
|
// Mock decryption by XORing with the key bytes
|
|
// This is NOT secure and is only for demonstration purposes
|
|
const keyBytes = cryptoService.hexToBytes(key);
|
|
const decryptedBytes = new Uint8Array(encryptedBytes.length);
|
|
|
|
for (let i = 0; i < encryptedBytes.length; i++) {
|
|
decryptedBytes[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
}
|
|
|
|
return decryptedBytes;
|
|
}
|
|
}
|
|
|
|
// Create a singleton instance
|
|
export const contentProcessorService = new ContentProcessorService();
|