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

View File

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

4919
sweb/pnpm-lock.yaml generated

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">
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";
// Default to introduction if no path is provided
$: actualPath = contentPath || "introduction/introduction";
// Flag to show IPFS demo
let showIPFSDemo = false;
</script>
<div class="">
@@ -20,35 +15,17 @@
<MarkdownContent path={actualPath} />
</div>
{:else}
<!-- IPFS Demo Section -->
<section class="mb-12 sm:mb-16 md:mb-20">
<div class="text-center mb-8 sm:mb-12">
<h2
class="text-2xl sm:text-3xl font-bold mb-3 sm:mb-4 text-text"
>
IPFS Integration
</h2>
<p
class="text-lg sm:text-xl text-text-secondary max-w-3xl mx-auto"
>
Experience the power of decentralized content storage and
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>
<div class="p-8 text-center">
<h2 class="text-2xl sm:text-3xl font-bold mb-4 text-text">
Welcome to SecureWeb
</h2>
<p class="text-lg text-text-secondary max-w-3xl mx-auto">
A decentralized web platform with IPFS integration and Blake
hashing for content security.
</p>
<p class="mt-4 text-text-secondary">
Use the sidebar navigation to explore the content and demos.
</p>
</div>
{/if}
</div>

View File

@@ -4,6 +4,8 @@
import Footer from "./Footer.svelte";
import NavDataProvider from "./NavDataProvider.svelte";
import Home from "./Home.svelte";
import BlakeHashDemo from "./BlakeHashDemo.svelte";
import IPFSDemo from "./IPFSDemo.svelte";
import { onMount } from "svelte";
import ThemeProvider from "../lib/theme/ThemeProvider.svelte";
@@ -82,7 +84,15 @@
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} />
{:else}
<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();