feat: Add implementation details and dependencies

- 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.
This commit is contained in:
Mahmoud Emad 2025-05-13 10:42:18 +03:00
parent 2c16c8bc11
commit 773fd2e2bf
8 changed files with 5488 additions and 51 deletions

69
impl.md Normal file
View File

@ -0,0 +1,69 @@
# Implementation Status Report
## Analysis of Implementation Status
### What Has Been Implemented:
1. **Basic Website Structure**:
- Svelte-based frontend with components for layout, navigation, and content display
- Responsive design with mobile support
- Markdown content rendering capability
2. **IPFS Integration**:
- IPFS service implementation using Helia library
- Content retrieval from IPFS
- Content upload to IPFS
- Fallback to IPFS gateways when direct connection fails
- Local caching of IPFS content using IndexedDB
- Network status monitoring for offline support
3. **UI Components**:
- Header/Navbar
- Sidebar navigation
- Footer
- Markdown content display
- IPFS demo functionality
4. **Demo Functionality**:
- IPFS content upload and retrieval demo
- Mock functionality when IPFS is not available
### What Is Missing:
1. **Content Processing Pipeline**:
- No implementation of the Blake hashing algorithm for content
- No implementation of content encryption/decryption
- No CLI tools or scripts for processing content files as described in specs
- Missing the pipeline for file discovery, normalization, hashing, encryption, and IPFS upload
2. **Metadata Structure**:
- No implementation of the metadata structure as described in specs
- Missing the pages list metadata with Blake hash + IPFS CID format
- No implementation of metadata retrieval from IPFS
3. **Security Features**:
- Missing the encryption/decryption functionality using Blake hash as key
- No implementation of the security considerations mentioned in specs
4. **Collection Structure**:
- No implementation of the collection structure as described in specs
- Missing the `.collection` file handling
5. **Content Retrieval with Decryption**:
- No implementation of retrieving encrypted content and decrypting it
## Conclusion
The project has implemented a solid foundation for a browser-based website with IPFS integration, including:
- Basic website structure and UI components
- IPFS content retrieval and upload
- Local caching and offline support
- Demo functionality
However, the core security features described in the specifications are not implemented:
- The content processing pipeline with Blake hashing and encryption
- The metadata structure with combined Blake hash and IPFS CID
- The collection structure for organizing content
- The encryption/decryption functionality
The current implementation provides a working demo of IPFS integration but lacks the security features that are central to the project's specifications. To fully meet the requirements, the missing components would need to be implemented, particularly the content processing pipeline with encryption and the metadata structure.

View File

@ -31,6 +31,7 @@
"@libp2p/webrtc": "^5.2.12", "@libp2p/webrtc": "^5.2.12",
"@libp2p/websockets": "^9.2.10", "@libp2p/websockets": "^9.2.10",
"@libp2p/webtransport": "^5.0.40", "@libp2p/webtransport": "^5.0.40",
"@noble/hashes": "^1.8.0",
"@tailwindcss/postcss": "^4.1.6", "@tailwindcss/postcss": "^4.1.6",
"@tailwindcss/vite": "^4.1.6", "@tailwindcss/vite": "^4.1.6",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
<script lang="ts">
import { cryptoService } from '../services/crypto.service';
import { contentProcessorService } from '../services/content-processor.service';
import { ipfsService } from '../services/ipfs.service';
import { onMount } from 'svelte';
let textInput = '';
let blakeHash = '';
let selectedFile: File | null = null;
let fileBlakeHash = '';
let normalizedFilename = '';
let ipfsCid = '';
let combinedKey = '';
let isProcessing = false;
let errorMessage = '';
let successMessage = '';
// Hash the text input using Blake3
function hashText() {
try {
errorMessage = '';
if (!textInput.trim()) {
errorMessage = 'Please enter some text to hash';
return;
}
blakeHash = cryptoService.hashStringBlake3AsHex(textInput);
successMessage = 'Text hashed successfully!';
} catch (error) {
console.error('Error hashing text:', error);
errorMessage = `Error hashing text: ${error instanceof Error ? error.message : String(error)}`;
}
}
// Handle file selection
function handleFileSelect(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
selectedFile = input.files[0];
// Reset previous results
fileBlakeHash = '';
normalizedFilename = '';
ipfsCid = '';
combinedKey = '';
errorMessage = '';
successMessage = '';
}
}
// Process the selected file
async function processFile() {
if (!selectedFile) {
errorMessage = 'Please select a file to process';
return;
}
try {
isProcessing = true;
errorMessage = '';
successMessage = '';
// Normalize the filename
normalizedFilename = contentProcessorService.normalizeFilename(selectedFile.name);
// Hash the file using Blake3
fileBlakeHash = await cryptoService.hashFileBlake3AsHex(selectedFile);
// Process the file through the content processing pipeline
const metadata = await contentProcessorService.processFile(selectedFile);
// Update the UI with the results
ipfsCid = metadata.ipfsCid;
combinedKey = metadata.combinedKey;
isProcessing = false;
successMessage = 'File processed successfully!';
} catch (error) {
console.error('Error processing file:', error);
errorMessage = `Error processing file: ${error instanceof Error ? error.message : String(error)}`;
isProcessing = false;
}
}
// Initialize IPFS when the component mounts
onMount(async () => {
try {
await ipfsService.initialize();
} catch (error) {
console.error('Error initializing IPFS:', error);
errorMessage = `Error initializing IPFS: ${error instanceof Error ? error.message : String(error)}`;
}
});
</script>
<div class="p-6 max-w-4xl mx-auto bg-white rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-6">Blake Hashing Demo</h2>
<!-- Text Input Section -->
<div class="mb-8 p-4 border border-gray-200 rounded-md">
<h3 class="text-xl font-semibold mb-4">Text Hashing</h3>
<div class="mb-4">
<label for="textInput" class="block text-sm font-medium text-gray-700 mb-1">Enter text to hash:</label>
<textarea
id="textInput"
bind:value={textInput}
class="w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
rows="3"
></textarea>
</div>
<button
on:click={hashText}
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
Hash Text
</button>
{#if blakeHash}
<div class="mt-4">
<h4 class="text-sm font-medium text-gray-700 mb-1">Blake3 Hash:</h4>
<div class="p-2 bg-gray-100 rounded-md overflow-x-auto">
<code class="text-sm break-all">{blakeHash}</code>
</div>
</div>
{/if}
</div>
<!-- File Processing Section -->
<div class="p-4 border border-gray-200 rounded-md">
<h3 class="text-xl font-semibold mb-4">File Processing</h3>
<div class="mb-4">
<label for="fileInput" class="block text-sm font-medium text-gray-700 mb-1">Select a file to process:</label>
<input
type="file"
id="fileInput"
on:change={handleFileSelect}
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
/>
</div>
<button
on:click={processFile}
disabled={!selectedFile || isProcessing}
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-blue-300 disabled:cursor-not-allowed"
>
{isProcessing ? 'Processing...' : 'Process File'}
</button>
{#if selectedFile && (normalizedFilename || fileBlakeHash || ipfsCid || combinedKey)}
<div class="mt-4 space-y-3">
{#if normalizedFilename}
<div>
<h4 class="text-sm font-medium text-gray-700 mb-1">Normalized Filename:</h4>
<div class="p-2 bg-gray-100 rounded-md overflow-x-auto">
<code class="text-sm break-all">{normalizedFilename}</code>
</div>
</div>
{/if}
{#if fileBlakeHash}
<div>
<h4 class="text-sm font-medium text-gray-700 mb-1">Blake3 Hash:</h4>
<div class="p-2 bg-gray-100 rounded-md overflow-x-auto">
<code class="text-sm break-all">{fileBlakeHash}</code>
</div>
</div>
{/if}
{#if ipfsCid}
<div>
<h4 class="text-sm font-medium text-gray-700 mb-1">IPFS CID:</h4>
<div class="p-2 bg-gray-100 rounded-md overflow-x-auto">
<code class="text-sm break-all">{ipfsCid}</code>
</div>
</div>
{/if}
{#if combinedKey}
<div>
<h4 class="text-sm font-medium text-gray-700 mb-1">Combined Key (Blake Hash + IPFS CID):</h4>
<div class="p-2 bg-gray-100 rounded-md overflow-x-auto">
<code class="text-sm break-all">{combinedKey}</code>
</div>
</div>
{/if}
</div>
{/if}
</div>
<!-- Messages -->
{#if errorMessage}
<div class="mt-4 p-3 bg-red-100 text-red-700 rounded-md">
{errorMessage}
</div>
{/if}
{#if successMessage}
<div class="mt-4 p-3 bg-green-100 text-green-700 rounded-md">
{successMessage}
</div>
{/if}
</div>

View File

@ -1,15 +1,10 @@
<script lang="ts"> <script lang="ts">
import MarkdownContent from "./MarkdownContent.svelte"; import MarkdownContent from "./MarkdownContent.svelte";
import IPFSDemo from "./IPFSDemo.svelte";
import Button from "$lib/components/ui/button/button.svelte";
export let contentPath: string = "introduction/introduction"; export let contentPath: string = "introduction/introduction";
// Default to introduction if no path is provided // Default to introduction if no path is provided
$: actualPath = contentPath || "introduction/introduction"; $: actualPath = contentPath || "introduction/introduction";
// Flag to show IPFS demo
let showIPFSDemo = false;
</script> </script>
<div class=""> <div class="">
@ -20,35 +15,17 @@
<MarkdownContent path={actualPath} /> <MarkdownContent path={actualPath} />
</div> </div>
{:else} {:else}
<!-- IPFS Demo Section --> <div class="p-8 text-center">
<section class="mb-12 sm:mb-16 md:mb-20"> <h2 class="text-2xl sm:text-3xl font-bold mb-4 text-text">
<div class="text-center mb-8 sm:mb-12"> Welcome to SecureWeb
<h2 </h2>
class="text-2xl sm:text-3xl font-bold mb-3 sm:mb-4 text-text" <p class="text-lg text-text-secondary max-w-3xl mx-auto">
> A decentralized web platform with IPFS integration and Blake
IPFS Integration hashing for content security.
</h2> </p>
<p <p class="mt-4 text-text-secondary">
class="text-lg sm:text-xl text-text-secondary max-w-3xl mx-auto" Use the sidebar navigation to explore the content and demos.
> </p>
Experience the power of decentralized content storage and </div>
retrieval with IPFS.
</p>
<div class="mt-6">
<Button
variant="primary"
size="lg"
on:click={() => (showIPFSDemo = !showIPFSDemo)}
>
{showIPFSDemo ? "Hide IPFS Demo" : "Show IPFS Demo"}
</Button>
</div>
</div>
{#if showIPFSDemo}
<IPFSDemo />
{/if}
</section>
{/if} {/if}
</div> </div>

View File

@ -4,6 +4,8 @@
import Footer from "./Footer.svelte"; import Footer from "./Footer.svelte";
import NavDataProvider from "./NavDataProvider.svelte"; import NavDataProvider from "./NavDataProvider.svelte";
import Home from "./Home.svelte"; import Home from "./Home.svelte";
import BlakeHashDemo from "./BlakeHashDemo.svelte";
import IPFSDemo from "./IPFSDemo.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import ThemeProvider from "../lib/theme/ThemeProvider.svelte"; import ThemeProvider from "../lib/theme/ThemeProvider.svelte";
@ -82,7 +84,15 @@
sidebarVisible && !isMobile ? "md:ml-64" : "ml-0" sidebarVisible && !isMobile ? "md:ml-64" : "ml-0"
}`} }`}
> >
{#if selectedContentPath} {#if selectedContentPath === "demos/blake-hash-demo"}
<div class="p-4">
<BlakeHashDemo />
</div>
{:else if selectedContentPath === "demos/ipfs-demo"}
<div class="p-4">
<IPFSDemo />
</div>
{:else if selectedContentPath}
<Home contentPath={selectedContentPath} /> <Home contentPath={selectedContentPath} />
{:else} {:else}
<slot /> <slot />

View File

@ -0,0 +1,168 @@
/**
* 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();

View File

@ -0,0 +1,125 @@
/**
* Cryptography Service
* Provides cryptographic functions for content processing
* Implements Blake hashing algorithm for content integrity and encryption key generation
*/
import { blake3 } from '@noble/hashes/blake3';
import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
/**
* Service for cryptographic operations
* Provides methods for hashing, encryption, and decryption
*/
class CryptoService {
/**
* Hash content using Blake3 algorithm
* @param content - Content to hash as Uint8Array
* @returns Blake3 hash as Uint8Array
*/
hashBlake3(content: Uint8Array): Uint8Array {
return blake3(content);
}
/**
* Hash content using Blake3 algorithm with custom output length
* @param content - Content to hash as Uint8Array
* @param outputLength - Length of the output hash in bytes
* @returns Blake3 hash as Uint8Array
*/
hashBlake3WithLength(content: Uint8Array, outputLength: number): Uint8Array {
return blake3(content, { dkLen: outputLength });
}
/**
* Hash string content using Blake3 algorithm
* @param content - Content to hash as string
* @returns Blake3 hash as Uint8Array
*/
hashStringBlake3(content: string): Uint8Array {
const contentBytes = utf8ToBytes(content);
return this.hashBlake3(contentBytes);
}
/**
* Hash string content using Blake3 algorithm and return as hex string
* @param content - Content to hash as string
* @returns Blake3 hash as hex string
*/
hashStringBlake3AsHex(content: string): string {
const hash = this.hashStringBlake3(content);
return bytesToHex(hash);
}
/**
* Hash file content using Blake3 algorithm
* @param file - File to hash
* @returns Promise<Uint8Array> - Blake3 hash as Uint8Array
*/
async hashFileBlake3(file: File): Promise<Uint8Array> {
const buffer = await file.arrayBuffer();
const contentBytes = new Uint8Array(buffer);
return this.hashBlake3(contentBytes);
}
/**
* Hash file content using Blake3 algorithm and return as hex string
* @param file - File to hash
* @returns Promise<string> - Blake3 hash as hex string
*/
async hashFileBlake3AsHex(file: File): Promise<string> {
const hash = await this.hashFileBlake3(file);
return bytesToHex(hash);
}
/**
* Combine Blake hash and IPFS CID into a single key string
* Format: <blake_hash>:<ipfs_cid>
* @param blakeHash - Blake hash as hex string
* @param ipfsCid - IPFS CID as string
* @returns Combined key string
*/
combineBlakeHashAndIpfsCid(blakeHash: string, ipfsCid: string): string {
return `${blakeHash}:${ipfsCid}`;
}
/**
* Split combined key string into Blake hash and IPFS CID
* @param combinedKey - Combined key string in format <blake_hash>:<ipfs_cid>
* @returns Object with blakeHash and ipfsCid
*/
splitCombinedKey(combinedKey: string): { blakeHash: string; ipfsCid: string } {
const [blakeHash, ipfsCid] = combinedKey.split(':');
return { blakeHash, ipfsCid };
}
/**
* Convert hex string to Uint8Array
* @param hex - Hex string
* @returns Uint8Array
*/
hexToBytes(hex: string): Uint8Array {
return hexToBytes(hex);
}
/**
* Convert Uint8Array to hex string
* @param bytes - Uint8Array
* @returns Hex string
*/
bytesToHex(bytes: Uint8Array): string {
return bytesToHex(bytes);
}
/**
* Convert string to Uint8Array using UTF-8 encoding
* @param str - String to convert
* @returns Uint8Array
*/
stringToBytes(str: string): Uint8Array {
return utf8ToBytes(str);
}
}
// Create a singleton instance
export const cryptoService = new CryptoService();