-
- **Still stuck? Don't worry!**
-
- **Open an issue on GitHub with detailed error logs**
-
- ***
-
- *Troubleshooting guide last updated: March 08, 2026*
-
- ***
-
- > [!NOTE]
- > ** TROUBLESHOOTING STATUS: UPDATED**
- >
- > **Architecture Version:** 2.1.0
- >
- > **Current Edited Date:** `2026-03-08`
-
diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md
deleted file mode 100644
index e185413..0000000
--- a/docs/TUTORIAL.md
+++ /dev/null
@@ -1,1981 +0,0 @@
-๏ปฟ# Complete Tutorial: Blockchain-Based Verifiable Credentials System
-
-**Version 2.1** | A comprehensive guide to building, understanding, and using a blockchain-based academic credential verification platform (Elite UI/UX Edition)
-
-***
-
-## Table of Contents
-
-1. [Introduction](#introduction)
-2. [System Requirements](#system-requirements)
-3. [Understanding Core Concepts](#understanding-core-concepts)
-4. [Installation Guide](#installation-guide)
-5. [System Architecture Deep Dive](#system-architecture-deep-dive)
-6. [Implementation Walkthrough](#implementation-walkthrough)
-7. [Usage Scenarios](#usage-scenarios)
-8. [Security Analysis](#security-analysis)
-9. [Troubleshooting](#troubleshooting)
-10. [Advanced Topics](#advanced-topics)
-11. [Best Practices](#best-practices)
-12. [Conclusion](#conclusion)
-
-***
-
-## Introduction
-
-### What is This System?
-
-This tutorial provides a complete, step-by-step guide to building and deploying a **production-ready blockchain-based verifiable credential system** for academic transcripts. The system demonstrates real-world applications of cutting-edge technologies:
-
-- **Blockchain Technology** Immutable, tamper-proof record keeping
-- **IPFS (InterPlanetary File System)** Decentralized credential storage
-- **RSA-2048 Cryptography** Digital signatures for authenticity
-- **W3C Verifiable Credentials** Industry-standard credential format
-- **Zero-Knowledge Proofs** Privacy-preserving selective disclosure
-- **Elite 10/10 PDF Engine** Senior-grade academic document generation (March 2026)
-
-
-### The Problem We're Solving
-
-**Traditional Academic Credential Verification:**
-
-- Slow (days to weeks for verification)
-- Expensive (manual administrative overhead)
-- Fraud-prone (certificates can be forged)
-- Privacy-invasive (full transcript exposure required)
-- Centralized (single points of failure)
-
-**Our Blockchain Solution:**
-
-- Instant verification (< 2 seconds)
-- Cost-effective (automated process)
-- Tamper-proof (cryptographic guarantees)
-- Privacy-preserving (selective disclosure)
-- Decentralized (no single authority)
-
-
-### Project Context
-
-**Academic Information:**
-
-- **Institution:** G. Pulla Reddy Engineering College (Autonomous), Kurnool
-- **Department:** Computer Science Engineering
-- **Project Type:** B.Tech Final Year Project
-- **Version:** 2.2 (Private PVT Blockchain - Master Security Edition)
-
-**Development Team:**
-
-- **Backend \& Blockchain:** [@udaycodespace](https://github.com/udaycodespace)
-- **Frontend \& IPFS:** [@shashikiran47](https://github.com/shashikiran47)
-- **Testing \& Documentation:** [@tejavarshith](https://github.com/tejavarshith)
-
-***
-
-## System Requirements
-
-### Software Prerequisites
-
-#### Required Software
-
-| Software | Minimum Version | Purpose |
-| :-- | :-- | :-- |
-| **Python** | 3.10+ | Core programming language |
-| **pip** | Latest | Python package manager |
-| **Git** | 2.30+ | Version control |
-| **Web Browser** | Chrome 90+ / Firefox 88+ | User interface |
-| **Code Editor** | VS Code (recommended) | Development environment |
-
-#### Operating System Support
-
-- **Windows 10/11** Full support
-- **macOS 11+** Full support
-- **Linux (Ubuntu 20.04+)** Full support
-
-
-### Hardware Requirements
-
-**Minimum Specifications:**
-
-- CPU: Dual-core processor
-- RAM: 4GB
-- Storage: 2GB free space
-- Network: Stable internet connection
-
-**Recommended Specifications:**
-
-- CPU: Quad-core processor
-- RAM: 8GB+
-- Storage: 5GB+ free space
-- Network: Broadband connection
-
-
-### Python Dependencies
-
-**Core Dependencies:**
-
-```txt
-Flask==3.0.0 # Web framework
-cryptography==41.0.7 # RSA encryption & signatures
-requests==2.31.0 # HTTP client for IPFS
-SQLAlchemy==2.0.23 # Database ORM
-Flask-Login==0.6.3 # Session management
-Werkzeug==3.0.1 # WSGI utilities
-Jinja2==3.1.2 # Template engine
-```
-
-**Development Dependencies:**
-
-```txt
-pytest==7.4.3 # Testing framework
-pytest-cov==4.1.0 # Coverage reporting
-black==23.12.1 # Code formatter
-flake8==7.0.0 # Code linter
-```
-
-**Install All Dependencies:**
-
-```bash
-pip install -r requirements.txt
-# New key modules included: pyotp (MFA), qrcode (Auth Setup)
-```
-
-
-***
-
-## Understanding Core Concepts
-
-### 1. Blockchain Fundamentals
-
-#### What is a Blockchain?
-
-A blockchain is a **distributed ledger** that maintains a growing list of records (blocks) linked using cryptography. Each block contains:
-
-```
-
- BLOCK #123
-
- Index: 123
- Timestamp: 2024-12-26T15:01:00Z
- Data: {credential_id: CRED_001}
- Previous Hash: abc123...
- Hash: def456...
- Nonce: 42857
-
-
- Cryptographically
- Linked
-
-
- BLOCK #124
-
- Previous Hash: def456... (from #123)
- ...
-
-```
-
-**Key Properties:**
-
-- **Immutability:** Once written, data cannot be changed
-- **Transparency:** All participants can verify the chain
-- **Distributed:** No single point of failure
-- **Secure:** Cryptographic hashing prevents tampering
-
-
-#### How Our Blockchain Works
-
-**1. Genesis Block Creation:**
-
-```python
-Genesis Block {
- index: 0,
- timestamp: "2024-01-01T00:00:00Z",
- data: {"type": "genesis"},
- previous_hash: "0",
- hash: "calculated_hash",
- nonce: 0
-}
-```
-
-**2. Adding New Blocks:**
-
-```python
-# When credential issued:
-New Block {
- index: current_index + 1,
- timestamp: current_time,
- data: {
- "credential_id": "CRED_001",
- "ipfs_cid": "Qm...",
- "issuer": "University Name",
- "action": "issue"
- },
- previous_hash: last_block.hash,
- hash: calculated_hash,
- nonce: found_by_mining
-}
-```
-
-**3. Proof-of-Work Mining:**
-
-```python
-# Find nonce where hash starts with '0000...'
-while not hash.startswith('0' * difficulty):
- nonce += 1
- hash = SHA256(block_data + nonce)
-```
-
-
-### 2. Cryptographic Signatures
-
-#### RSA-2048 Digital Signatures
-
-**How It Works:**
-
-```
-
- CREDENTIAL ISSUANCE
-
-
-
-
- 1. Create Credential Data
- {student: "John", gpa: 8.5, ...}
-
-
-
-
- 2. Hash the Data
- SHA256(data) hash_value
-
-
-
-
- 3. Sign with Private Key
- signature = RSA_Sign(hash, private_key)
-
-
-
-
- 4. Attach Signature to Credential
- credential.signature = signature
-
-
-
- CREDENTIAL VERIFICATION
-
-
-
-
- 1. Receive Credential + Signature
-
-
-
-
- 2. Extract Public Key
- public_key = get_issuer_public_key()
-
-
-
-
- 3. Verify Signature
- valid = RSA_Verify(data, signature,
- public_key)
-
-
-
- Valid / Invalid
-```
-
-**Security Guarantees:**
-
-- **Authenticity:** Only holder of private key could create signature
-- **Integrity:** Any data tampering invalidates signature
-- **Non-repudiation:** Signer cannot deny signing
-
-
-### 3. IPFS Storage
-
-#### What is IPFS?
-
-IPFS (InterPlanetary File System) is a **peer-to-peer distributed file system** that uses content-addressing.
-
-**Traditional Web (Location-based):**
-
-```
-URL: https://university.edu/transcript/john_doe.pdf
- File location changes? Link breaks!
-```
-
-**IPFS (Content-based):**
-
-```
-CID: QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG
- Content never changes, always accessible!
-```
-
-**How It Works:**
-
-```
-
- 1. Store Credential on IPFS
- credential IPFS.add()
-
-
-
-
- 2. IPFS Calculates Content Hash
- CID = SHA256(credential_data)
- QmYwAPJzv5CZ...
-
-
-
-
- 3. Store CID on Blockchain
- blockchain.add({cid: "Qm..."})
-
-
-
-
- 4. Retrieve Credential
- credential = IPFS.get(cid)
-
-```
-
-**Benefits:**
-
-- **Deduplication:** Identical content = same CID
-- **Distributed:** No single point of failure
-- **Immutable:** Content hash verifies integrity
-- **Permanent:** Content persists across network
-
-
-### 4. W3C Verifiable Credentials
-
-#### Standard Structure
-
-```json
-{
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "type": ["VerifiableCredential", "AcademicCredential"],
- "issuer": {
- "id": "did:example:university",
- "name": "G. Pulla Reddy Engineering College"
- },
- "issuanceDate": "2024-12-26T15:01:00Z",
- "credentialSubject": {
- "id": "did:example:student123",
- "name": "John Doe",
- "degree": "B.Tech Computer Science",
- "gpa": 8.5,
- "graduationYear": 2025
- },
- "proof": {
- "type": "RsaSignature2018",
- "created": "2024-12-26T15:01:00Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "issuer_public_key",
- "jws": "eyJhbGc...signature_here"
- }
-}
-```
-
-
-### 5. Selective Disclosure \& Zero-Knowledge Proofs
-
-#### Selective Disclosure
-
-**Concept:** Share only required fields, hide everything else
-
-**Example Scenario:**
-
-```
-Full Credential:
- Name: John Doe
- Student ID: CST001
- DOB: 1999-05-15
- Degree: B.Tech CS
- GPA: 8.5
- Marks: [85, 90, 88, ...]
- Address: 123 Main St
-
-Selective Disclosure (Share only GPA):
- GPA: 8.5
- All other fields hidden
-
-Verifier sees:
-{
- "gpa": 8.5,
- "proof": "cryptographic_proof_that_gpa_is_valid"
-}
-```
-
-
-#### Zero-Knowledge Proofs
-
-**Concept:** Prove a statement is true without revealing the data
-
-**Example:**
-
-```
-Statement: "My GPA is above 7.5"
-Proof: Generate cryptographic proof
-Result: Verifier confirms GPA > 7.5
- WITHOUT knowing actual GPA (8.5)
-```
-
-**Types Implemented:**
-
-1. **Range Proofs:**
-
-```python
-Prove: 7.5 GPA 10.0
-Without revealing: actual GPA = 8.5
-```
-
-2. **Membership Proofs:**
-
-```python
-Prove: degree ["B.Tech", "M.Tech", "PhD"]
-Without revealing: actual degree = "B.Tech"
-```
-
-
-***
-
-## Installation Guide
-
-### Step 1: System Preparation
-
-#### Windows
-
-```powershell
-# Check Python version
-python --version
-# Should show Python 3.10.x or higher
-
-# Update pip
-python -m pip install --upgrade pip
-
-# Install Git (if not installed)
-# Download from: https://git-scm.com/download/win
-```
-
-
-#### macOS
-
-```bash
-# Install Homebrew (if not installed)
-/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
-# Install Python
-brew install python@3.11
-
-# Check version
-python3 --version
-```
-
-
-#### Linux (Ubuntu/Debian)
-
-```bash
-# Update package list
-sudo apt update
-
-# Install Python and pip
-sudo apt install python3.11 python3-pip
-
-# Install development tools
-sudo apt install python3-dev build-essential
-
-# Check version
-python3 --version
-```
-
-
-### Step 2: Clone Repository
-
-```bash
-# Clone the project
-git clone https://github.com/udaycodespace/credify.git
-cd credify
-
-# Or download ZIP and extract
-```
-
-
-### Step 3: Create Virtual Environment
-
-**Why use virtual environment?**
-
-- Isolates project dependencies
-- Prevents conflicts with system Python
-- Easy dependency management
-
-```bash
-# Create virtual environment
-python -m venv venv
-
-# Activate virtual environment
-
-# Windows (PowerShell)
-venv\Scripts\Activate.ps1
-
-# Windows (CMD)
-venv\Scripts\activate.bat
-
-# macOS/Linux
-source venv/bin/activate
-
-# Verify activation (prompt should show (venv))
-```
-
-
-### Step 4: Install Dependencies
-
-```bash
-# Install production dependencies
-pip install -r requirements.txt
-
-# Install development dependencies (optional)
-pip install -r requirements-dev.txt
-
-# Verify installations
-pip list
-```
-
-
-### Step 5: Configure Environment
-
-```bash
-# Create .env file
-# Windows
-copy .env.example .env
-
-# macOS/Linux
-cp .env.example .env
-
-# Edit .env with your settings
-```
-
-**Example `.env` Configuration:**
-
-```bash
-# Flask Configuration
-FLASK_ENV=development
-DEBUG=True
-
-# Security Keys (CHANGE THESE!)
-SECRET_KEY=xyz
-SESSION_SECRET=xyz
-
-# Database
-DATABASE_URL=sqlite:///credentials.db
-
-# Server
-HOST=0.0.0.0
-PORT=5000
-
-# IPFS (Optional)
-IPFS_ENABLED=False
-
-# Initial Admin Secrets (Optional - System will randomize if missing)
-INITIAL_ADMIN_PASSWORD=your_secure_password
-INITIAL_ISSUER_PASSWORD=your_secure_issuer_password
-```
-
-
-### Step 6: Initialize Database
-
-```bash
-# Initialize database schema
-python -c "from app.models import init_database; from app.app import app; init_database(app)"
-
-# Verify database created
-ls instance/ # Should show credentials.db
-
-# Or using Makefile
-make init-db
-```
-
-
-### Step 7: Create Admin User
-
-```bash
-# Start the application
-python main.py
-
-# ON FIRST BOOT:
-# 1. Look at your terminal logs!
-# 2. The system will print: " GENERATED SECURE ADMIN PASSWORD: [random_key]"
-# 3. Use this key to login at http://localhost:5000/issuer
-# 4. Follow the MFA setup prompt to link your phone.
-```
-
-
-### Step 8: Start Application
-
-```bash
-# Start Flask development server
-python main.py
-
-# Or using Makefile
-make run
-
-# Expected output:
-# Application initialized successfully!
-# Starting server...
-# * Running on http://127.0.0.1:5000
-```
-
-
-### Step 9: Verify Installation
-
-**Open browser and test:**
-
-1. **Home Page:** http://localhost:5000
- - Should show landing page
-2. **Login:** http://localhost:5000/login
- - Login with admin credentials
-3. **Issuer Dashboard:** http://localhost:5000/issuer
- - Should show credential issuance form
-4. **Verifier:** http://localhost:5000/verifier
- - Should show verification interface
-
-**If all pages load successfully, installation is complete!**
-
-***
-
-## System Architecture Deep Dive
-
-### Overall System Design
-
-```
-
- USER INTERFACE LAYER
-
- Issuer Student Verifier
- Dashboard Dashboard Portal
-
-
-
-
- APPLICATION LAYER (Flask)
-
-
- Route Handlers
- Authentication Authorization
- Session Mgmt Error Handling
-
-
-
-
- BUSINESS LOGIC LAYER
-
-
- Credential Manager
- Issue Verify Revoke Disclose
-
-
-
-
- Crypto Block- IPFS ZKP
- Utils chain Client Manager
-
-
-
-
- PERSISTENCE LAYER
-
-
- SQLite IPFS Blockchain JSON Keys
- DB Store Store PEM
-
-
-```
-
-
-### Component Interaction Flow
-
-**Credential Issuance:**
-
-```
-User Flask Route Credential Manager
-
- Validate Data
-
- Generate W3C Credential
-
-
-
- Crypto Manager IPFS Client
- (Sign credential) (Store data)
-
-
-
- Blockchain Engine
- (Record hash)
-
- Update Registry Response
-```
-
-
-### Data Flow Diagram
-
-```
-
- CREDENTIAL DATA LIFECYCLE
-
-
-[^1] CREATION
- University inputs data
-
- Credential Manager creates W3C credential
-
- Crypto Manager signs with RSA-2048
-
- [Credential + Signature]
-
-[^2] STORAGE
- [Credential + Signature]
- IPFS (full data) returns CID
- Blockchain (hash + CID)
-
- Credential Registry (metadata)
-
-[^3] RETRIEVAL
- Request with Credential ID
-
- Registry lookup get CID + blockchain hash
-
- IPFS retrieval get full credential
-
- Blockchain verification confirm integrity
-
-[^4] VERIFICATION
- Full Credential
- Verify signature (Crypto Manager)
- Verify hash (Blockchain)
- Check revocation status (Registry)
-
- [Valid / Invalid / Revoked]
-```
-
-
-***
-
-## Implementation Walkthrough
-
-### Part 1: Blockchain Implementation
-
-#### Understanding the Block Structure
-
-```python
-# core/blockchain.py
-
-class Block:
- """
- Represents a single block in the blockchain.
- Each block contains:
- - index: Position in chain
- - timestamp: When block was created
- - data: Credential metadata
- - previous_hash: Link to previous block
- - hash: This block's unique identifier
- - nonce: Proof-of-work solution
- """
-
- def __init__(self, index, timestamp, data, previous_hash):
- self.index = index
- self.timestamp = timestamp
- self.data = data
- self.previous_hash = previous_hash
- self.nonce = 0
- self.hash = self.calculate_hash()
-
- def calculate_hash(self):
- """
- Creates SHA-256 hash of block data.
- Hash = SHA256(index + timestamp + data + prev_hash + nonce)
- """
- block_string = f"{self.index}{self.timestamp}{json.dumps(self.data)}{self.previous_hash}{self.nonce}"
- return hashlib.sha256(block_string.encode()).hexdigest()
-
- def mine_block(self, difficulty):
- """
- Proof-of-Work: Find nonce where hash starts with
- 'difficulty' number of zeros.
- Example: difficulty=4 hash must start with "0000"
- """
- target = '0' * difficulty
- while self.hash[:difficulty] != target:
- self.nonce += 1
- self.hash = self.calculate_hash()
-
- print(f" Block mined: {self.hash}")
-```
-
-
-#### Creating the Blockchain
-
-```python
-class SimpleBlockchain:
- """
- Manages the blockchain:
- - Creates genesis block
- - Adds new blocks
- - Validates chain integrity
- """
-
- def __init__(self, difficulty=4):
- self.chain = []
- self.difficulty = difficulty
- self.create_genesis_block()
-
- def create_genesis_block(self):
- """
- First block in chain - hardcoded initial state
- """
- genesis = Block(0, datetime.now().isoformat(),
- {"type": "genesis"}, "0")
- self.chain.append(genesis)
-
- def add_block(self, data):
- """
- Adds new block with credential data:
- 1. Get previous block
- 2. Create new block
- 3. Mine block (proof-of-work)
- 4. Append to chain
- 5. Save to file
- """
- previous_block = self.get_latest_block()
- new_block = Block(
- len(self.chain),
- datetime.now().isoformat(),
- data,
- previous_block.hash
- )
- new_block.mine_block(self.difficulty)
- self.chain.append(new_block)
- self.save_chain()
- return new_block
-
- def is_chain_valid(self):
- """
- Validates entire chain:
- - Check each block's hash is correct
- - Check links between blocks are valid
- - Detect any tampering
- """
- for i in range(1, len(self.chain)):
- current = self.chain[i]
- previous = self.chain[i-1]
-
- # Verify block hash
- if current.hash != current.calculate_hash():
- return False
-
- # Verify link to previous block
- if current.previous_hash != previous.hash:
- return False
-
- return True
-```
-
-
-### Part 2: Cryptographic Implementation
-
-#### RSA Key Generation
-
-```python
-# core/crypto_utils.py
-
-from cryptography.hazmat.primitives.asymmetric import rsa, padding
-from cryptography.hazmat.primitives import hashes, serialization
-
-class CryptoManager:
- """
- Handles all cryptographic operations:
- - RSA key generation
- - Digital signatures
- - Signature verification
- - Data hashing
- """
-
- def __init__(self):
- self.private_key = None
- self.public_key = None
- self.load_or_generate_keys()
-
- def generate_keys(self):
- """
- Generates RSA-2048 key pair:
- - Private key: For signing credentials
- - Public key: For verification (distributed publicly)
- """
- self.private_key = rsa.generate_private_key(
- public_exponent=65537,
- key_size=2048
- )
- self.public_key = self.private_key.public_key()
-
- # Save keys to PEM files
- self.save_keys()
-
- def sign_data(self, data):
- """
- Creates digital signature:
- 1. Hash the data (SHA-256)
- 2. Encrypt hash with private key
- 3. Return signature (base64 encoded)
-
- This proves:
- - Data came from holder of private key
- - Data hasn't been tampered with
- """
- if isinstance(data, dict):
- data = json.dumps(data, sort_keys=True)
-
- signature = self.private_key.sign(
- data.encode('utf-8'),
- padding.PSS(
- mgf=padding.MGF1(hashes.SHA256()),
- salt_length=padding.PSS.MAX_LENGTH
- ),
- hashes.SHA256()
- )
-
- return base64.b64encode(signature).decode('utf-8')
-
- def verify_signature(self, data, signature):
- """
- Verifies digital signature:
- 1. Decode signature from base64
- 2. Decrypt with public key
- 3. Compare with hash of data
-
- Returns: True if valid, False if tampered
- """
- try:
- if isinstance(data, dict):
- data = json.dumps(data, sort_keys=True)
-
- signature_bytes = base64.b64decode(signature)
-
- self.public_key.verify(
- signature_bytes,
- data.encode('utf-8'),
- padding.PSS(
- mgf=padding.MGF1(hashes.SHA256()),
- salt_length=padding.PSS.MAX_LENGTH
- ),
- hashes.SHA256()
- )
- return True
- except Exception:
- return False
-
- def hash_data(self, data):
- """
- Creates SHA-256 hash:
- - Deterministic (same input = same output)
- - One-way (cannot reverse)
- - Collision-resistant
-
- Used for: Blockchain integrity, data verification
- """
- if isinstance(data, dict):
- data = json.dumps(data, sort_keys=True)
-
- return hashlib.sha256(data.encode()).hexdigest()
-```
-
-
-### Part 3: IPFS Integration
-
-```python
-# core/ipfs_client.py
-
-class IPFSClient:
- """
- Manages IPFS storage with fallback:
- 1. Try local IPFS node
- 2. Try Infura gateway
- 3. Fall back to local JSON storage
- """
-
- def __init__(self):
- self.endpoints = [
- 'http://127.0.0.1:5001', # Local IPFS node
- 'https://ipfs.infura.io:5001' # Infura gateway
- ]
- self.local_storage = 'data/ipfs_storage.json'
- self.active_endpoint = None
- self._test_connection()
-
- def add_json(self, data):
- """
- Stores JSON data on IPFS:
- 1. Convert to JSON string
- 2. Upload to IPFS
- 3. Return Content ID (CID)
-
- CID = SHA256(data) content-addressed storage
- """
- for endpoint in self.endpoints:
- try:
- response = requests.post(
- f'{endpoint}/api/v0/add',
- files={'file': json.dumps(data)}
- )
- if response.status_code == 200:
- cid = response.json()['Hash']
- logging.info(f" Stored on IPFS: {cid}")
- return cid
- except Exception as e:
- logging.warning(f"IPFS endpoint {endpoint} failed: {e}")
-
- # Fallback to local storage
- return self._store_locally(data)
-
- def get_json(self, cid):
- """
- Retrieves data from IPFS:
- 1. Request data by CID
- 2. Parse JSON response
- 3. Return data
-
- If IPFS unavailable, retrieve from local storage
- """
- for endpoint in self.endpoints:
- try:
- response = requests.post(
- f'{endpoint}/api/v0/cat',
- params={'arg': cid}
- )
- if response.status_code == 200:
- return response.json()
- except Exception:
- continue
-
- # Fallback to local storage
- return self._retrieve_locally(cid)
-
- def _store_locally(self, data):
- """
- Local storage fallback:
- - Generates pseudo-CID (hash of data)
- - Stores in JSON file
- - Returns CID for consistency
- """
- cid = f"LOCAL_{hashlib.sha256(json.dumps(data).encode()).hexdigest()[:32]}"
-
- storage = self._load_local_storage()
- storage[cid] = data
- self._save_local_storage(storage)
-
- logging.info(f" Stored locally: {cid}")
- return cid
-```
-
-
-### Part 4: Credential Manager
-
-```python
-# core/credential_manager.py
-
-class CredentialManager:
- """
- Manages complete credential lifecycle:
- - Issuance (create + sign + store)
- - Verification (retrieve + validate)
- - Revocation (mark invalid)
- - Selective disclosure (privacy-preserving sharing)
- """
-
- def __init__(self, blockchain, crypto_manager, ipfs_client):
- self.blockchain = blockchain
- self.crypto = crypto_manager
- self.ipfs = ipfs_client
- self.registry = self._load_registry()
-
- def issue_credential(self, data):
- """
- Complete issuance flow:
-
- 1. Validate input data
- 2. Generate unique credential ID
- 3. Create W3C compliant credential
- 4. Sign with RSA-2048
- 5. Store on IPFS
- 6. Record hash on blockchain
- 7. Update credential registry
- 8. Return credential ID
- """
- # Step 1: Validate
- required_fields = ['student_name', 'student_id', 'degree',
- 'university', 'gpa', 'graduation_year']
- for field in required_fields:
- if field not in data:
- return {'success': False, 'error': f'Missing field: {field}'}
-
- # Step 2: Generate ID
- credential_id = f"CRED_{uuid.uuid4().hex[:16]}"
-
- # Step 3: Create W3C credential
- credential = {
- "@context": ["https://www.w3.org/2018/credentials/v1"],
- "type": ["VerifiableCredential", "AcademicCredential"],
- "id": credential_id,
- "issuer": {
- "id": f"did:example:{data['university'].lower().replace(' ', '_')}",
- "name": data['university']
- },
- "issuanceDate": datetime.now().isoformat() + 'Z',
- "credentialSubject": {
- "id": f"did:example:{data['student_id']}",
- "studentName": data['student_name'],
- "studentId": data['student_id'],
- "degree": data['degree'],
- "gpa": data['gpa'],
- "graduationYear": data['graduation_year']
- }
- }
-
- # Step 4: Sign credential
- signature = self.crypto.sign_data(credential)
- credential['proof'] = {
- "type": "RsaSignature2018",
- "created": datetime.now().isoformat() + 'Z',
- "proofPurpose": "assertionMethod",
- "jws": signature
- }
-
- # Step 5: Store on IPFS
- ipfs_cid = self.ipfs.add_json(credential)
-
- # Step 6: Record on blockchain
- blockchain_data = {
- "credential_id": credential_id,
- "ipfs_cid": ipfs_cid,
- "credential_hash": self.crypto.hash_data(credential),
- "issuer": data['university'],
- "action": "issue",
- "timestamp": datetime.now().isoformat()
- }
- block = self.blockchain.add_block(blockchain_data)
-
- # Step 7: Update registry
- self.registry[credential_id] = {
- "credential_id": credential_id,
- "student_id": data['student_id'],
- "ipfs_cid": ipfs_cid,
- "blockchain_hash": block.hash,
- "issue_date": datetime.now().isoformat(),
- "issuer": data['university'],
- "status": "active",
- "version": 1
- }
- self._save_registry()
-
- # Step 8: Return success
- logging.info(f" Issued credential: {credential_id}")
- return {
- 'success': True,
- 'credential_id': credential_id,
- 'ipfs_cid': ipfs_cid,
- 'blockchain_hash': block.hash
- }
-
- def verify_credential(self, credential_id):
- """
- Multi-layer verification:
-
- 1. Check registry (exists + not revoked)
- 2. Retrieve from IPFS
- 3. Verify cryptographic signature
- 4. Verify blockchain integrity
- 5. Return comprehensive result
- """
- # Step 1: Registry check
- if credential_id not in self.registry:
- return {'valid': False, 'error': 'Credential not found'}
-
- registry_entry = self.registry[credential_id]
-
- if registry_entry['status'] == 'revoked':
- return {
- 'valid': False,
- 'error': 'Credential has been revoked',
- 'revocation_date': registry_entry.get('revoked_date')
- }
-
- # Step 2: Retrieve from IPFS
- credential = self.ipfs.get_json(registry_entry['ipfs_cid'])
- if not credential:
- return {'valid': False, 'error': 'Could not retrieve credential'}
-
- # Step 3: Verify signature
- signature = credential['proof']['jws']
- credential_copy = credential.copy()
- del credential_copy['proof']
-
- is_signature_valid = self.crypto.verify_signature(
- credential_copy,
- signature
- )
-
- if not is_signature_valid:
- return {'valid': False, 'error': 'Invalid signature'}
-
- # Step 4: Verify blockchain
- expected_hash = self.crypto.hash_data(credential_copy)
- blockchain_valid = self.blockchain.is_chain_valid()
-
- # Step 5: Return result
- return {
- 'valid': True,
- 'credential': credential,
- 'issuer': registry_entry['issuer'],
- 'issue_date': registry_entry['issue_date'],
- 'blockchain_valid': blockchain_valid,
- 'signature_valid': is_signature_valid,
- 'status': registry_entry['status']
- }
-
- def selective_disclosure(self, credential_id, fields_to_disclose):
- """
- Privacy-preserving credential sharing:
-
- 1. Retrieve full credential
- 2. Extract only requested fields
- 3. Create cryptographic proof
- 4. Return minimal disclosure
-
- Example:
- Full: {name, id, dob, gpa, marks, address}
- Disclose: [gpa]
- Result: {gpa: 8.5, proof: "..."}
- """
- # Retrieve full credential
- verification = self.verify_credential(credential_id)
- if not verification['valid']:
- return {'success': False, 'error': verification['error']}
-
- credential = verification['credential']
- subject = credential['credentialSubject']
-
- # Extract requested fields only
- disclosed_data = {}
- for field in fields_to_disclose:
- if field in subject:
- disclosed_data[field] = subject[field]
-
- # Create proof
- proof = {
- "credential_id": credential_id,
- "disclosed_fields": disclosed_data,
- "hidden_field_count": len(subject) - len(disclosed_data),
- "issuer": credential['issuer']['name'],
- "issuance_date": credential['issuanceDate'],
- "proof_timestamp": datetime.now().isoformat() + 'Z',
- "proof_hash": self.crypto.hash_data(disclosed_data)
- }
-
- logging.info(f" Created selective disclosure for {credential_id}")
- return {'success': True, 'proof': proof}
-```
-
-
-***
-
-## Usage Scenarios
-
-### Scenario 1: University Issues Credential
-
-**Actors:** University Administrator (Issuer)
-
-**Steps:**
-
-1. **Login:**
-
-```
-URL: http://localhost:5000/login
-Username: admin
-Password: [admin_password]
-```
-
-2. **Navigate to Issuer Dashboard:**
-
-```
-URL: http://localhost:5000/issuer
-```
-
-3. **Fill Credential Form:**
-
-```
-Student Name: Alice Johnson
-Student ID: CST2024001
-Degree: B.Tech Computer Science and Engineering
-University: G. Pulla Reddy Engineering College
-GPA: 9.2
-Graduation Year: 2024
-Courses: Data Structures, Algorithms, DBMS, Computer Networks
-```
-
-4. **Submit \& Receive Confirmation:**
-
-```json
-{
- "success": true,
- "credential_id": "CRED_a1b2c3d4e5f6g7h8",
- "ipfs_cid": "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
- "blockchain_hash": "000012a3b4c5d6e7f8g9h0i1j2k3l4m5n6o7p8q9r0s1t2"
-}
-```
-
-5. **System Actions (Behind the Scenes):**
- - Credential created with W3C format
- - Signed with RSA-2048 private key
- - Stored on IPFS (full data)
- - Hash recorded on blockchain (block mined)
- - Registry updated with metadata
-
-***
-
-### Scenario 2: Student Views Credential
-
-**Actors:** Student (Holder)
-
-**Steps:**
-
-1. **Login:**
-
-```
-Username: CST2024001
-Password: [student_password]
-```
-
-2. **View Dashboard:**
-
-```
-URL: http://localhost:5000/holder
-```
-
-3. **See Credentials:**
-
-```
-
- My Credentials
-
- B.Tech Computer Science
- G. Pulla Reddy Engineering College
- GPA: 9.2 | Year: 2024
-
- [View Details] [Share] [Download PDF]
-
-```
-
-4. **View Full Details:**
-
-```json
-{
- "student_name": "Alice Johnson",
- "student_id": "CST2024001",
- "degree": "B.Tech Computer Science",
- "gpa": 9.2,
- "graduation_year": 2024,
- "courses": ["DS", "Algo", "DBMS", "Networks"],
- "issue_date": "2024-12-26T15:01:00Z",
- "issuer": "G. Pulla Reddy Engineering College",
- "status": " Active"
-}
-```
-
-
-***
-
-### Scenario 3: Student Creates Selective Disclosure
-
-**Actors:** Student (Holder), Employer (Verifier)
-
-**Steps:**
-
-1. **Student Selects Fields:**
-
-```
-Share with: TechCorp Inc.
-
-Select fields to disclose:
- Student Name
- Degree
- GPA
- Student ID (hidden)
- Courses (hidden)
- Date of Birth (hidden)
-```
-
-2. **Generate Proof:**
-
-```javascript
-// System creates minimal disclosure
-{
- "credential_id": "CRED_a1b2c3d4e5f6g7h8",
- "disclosed_fields": {
- "student_name": "Alice Johnson",
- "degree": "B.Tech Computer Science",
- "gpa": 9.2
- },
- "hidden_field_count": 3,
- "issuer": "G. Pulla Reddy Engineering College",
- "proof_hash": "abc123def456...",
- "proof_timestamp": "2024-12-26T15:30:00Z"
-}
-```
-
-3. **Student Shares Proof:**
- - Copy JSON proof
- - Send to employer via email/portal
-4. **Employer Verifies:**
-
-```
-URL: http://localhost:5000/verifier
-Paste proof JSON
-Click "Verify"
-```
-
-5. **Verification Result:**
-
-```
- CREDENTIAL VALID
-
-Student: Alice Johnson
-Degree: B.Tech Computer Science
-GPA: 9.2
-Issuer: G. Pulla Reddy Engineering College
-
- Additional fields hidden by student
- Cryptographic signature valid
- Blockchain integrity confirmed
- Not revoked
-```
-
-
-***
-
-### Scenario 4: Employer Verifies Full Credential
-
-**Actors:** Employer (Verifier)
-
-**Steps:**
-
-1. **Receive Credential ID:**
-
-```
-Student provides: CRED_a1b2c3d4e5f6g7h8
-```
-
-2. **Access Verifier Portal:**
-
-```
-URL: http://localhost:5000/verifier
-(No login required - public access)
-```
-
-3. **Submit for Verification:**
-
-```
-Enter Credential ID: CRED_a1b2c3d4e5f6g7h8
-Click: "Verify Credential"
-```
-
-4. **System Performs Checks:**
-
-```
-[1/5] Checking registry...
-[2/5] Retrieving from IPFS...
-[3/5] Verifying signature...
-[4/5] Checking blockchain...
-[5/5] Revocation check...
-```
-
-5. **View Complete Results:**
-
-```
-
- VERIFICATION SUCCESSFUL
-
- Student: Alice Johnson
- ID: CST2024001
- Degree: B.Tech Computer Science
- University: G.P.R. Engineering College
- GPA: 9.2 / 10.0
- Graduation: 2024
-
- Issued: 2024-12-26
- Status: Active
-
- Cryptographic Signature: Valid
- Blockchain Integrity: Confirmed
- IPFS Storage: Accessible
- Not Revoked
-
-```
-
-
-***
-
-### Scenario 5: University Revokes Credential
-
-**Actors:** University Administrator (Issuer)
-
-**When to Revoke:**
-
-- Student violated academic integrity
-- Credential issued in error
-- Data correction needed (issue new version instead)
-
-**Steps:**
-
-1. **Login as Issuer**
-2. **Navigate to Credential Management:**
-
-```
-View issued credentials
-Search for: CST2024001
-```
-
-3. **Select Credential:**
-
-```
-Credential: CRED_a1b2c3d4e5f6g7h8
-Status: Active
-[View] [Revoke]
-```
-
-4. **Revoke with Reason:**
-
-```
-Reason: Academic misconduct detected
-Category: Policy violation
-Additional notes: Disciplinary action taken
-
-[Confirm Revocation]
-```
-
-5. **System Actions:**
-
-```
-- Registry status: Active Revoked
-- Blockchain record: Revocation transaction added
-- Revocation date: 2024-12-26T16:00:00Z
-- Future verifications: Will show "Revoked"
-```
-
-6. **Future Verification Attempts:**
-
-```
- CREDENTIAL REVOKED
-
-This credential has been revoked by the issuer.
-
-Revocation Date: 2024-12-26
-Reason: Policy violation
-
-This credential is no longer valid for verification.
-```
-
-
-***
-
-## Security Analysis
-
-### Threat Model
-
-#### Threat 1: Credential Forgery
-
-**Attack:** Malicious actor creates fake credential
-
-**Defenses:**
-
-1. **RSA-2048 Signatures:**
- - Only issuer has private key
- - Signature verification fails for forgeries
-2. **Blockchain Integrity:**
- - All credentials recorded on blockchain
- - Fake credentials won't have blockchain entry
-3. **IPFS Content Addressing:**
- - CID is hash of content
- - Modified content = different CID
-
-**Result:** Forgery detected immediately
-
-#### Threat 2: Data Tampering
-
-**Attack:** Attacker modifies credential data (e.g., change GPA)
-
-**Defenses:**
-
-1. **Cryptographic Hash:**
- - Any change invalidates hash
- - Blockchain stores original hash
-2. **Digital Signature:**
- - Signature verification fails if data modified
- - RSA ensures authenticity
-3. **Immutable Blockchain:**
- - Original record preserved
- - Tampering is detectable
-
-**Result:** Tampering detected during verification
-
-#### Threat 3: Replay Attacks
-
-**Attack:** Reuse old credential for current purpose
-
-**Defenses:**
-
-1. **Timestamps:**
- - Issuance date recorded
- - Verifier can check validity period
-2. **Revocation System:**
- - Old/compromised credentials can be revoked
- - Verification checks revocation status
-3. **Credential Versioning:**
- - Multiple versions tracked
- - Latest version identifiable
-
-**Result:** Replay attacks mitigated
-
-#### Threat 4: Privacy Violations
-
-**Attack:** Unnecessarily expose full transcript
-
-**Defenses:**
-
-1. **Selective Disclosure:**
- - Student controls what to share
- - Only requested fields disclosed
-2. **Zero-Knowledge Proofs:**
- - Prove statements without data reveal
- - Example: Prove GPA > 7.5 without showing 8.5
-3. **Access Control:**
- - Students see only their credentials
- - Role-based permissions
-
-**Result:** Privacy preserved
-
-#### Threat 5: Man-in-the-Middle
-
-**Attack:** Intercept credential during transmission
-
-**Defenses:**
-
-1. **HTTPS (Production):**
- - Encrypted transport layer
- - TLS certificates
-2. **Digital Signatures:**
- - Even if intercepted, can't be modified
- - Signature verification protects integrity
-3. **Content-Addressed Storage:**
- - IPFS CID verifies data integrity
- - Modification detected
-
-**Result:** MITM attacks prevented
-
-### Security Best Practices
-
-#### For Development:
-
-```python
-# GOOD
-SECRET_KEY = os.environ.get('SECRET_KEY')
-DATABASE_URL = os.environ.get('DATABASE_URL')
-
-# BAD
-SECRET_KEY = "hardcoded-secret-key"
-DATABASE_URL = "sqlite:///prod.db"
-```
-
-
-#### For Production:
-
-```python
-# 1. Use environment variables
-# 2. Enable HTTPS
-# 3. Set secure session cookies
-# 4. Implement rate limiting
-# 5. Enable CORS properly
-# 6. Use strong passwords
-# 7. Regular security audits
-```
-
-
-***
-
-## Troubleshooting
-
-### Quick Fixes
-
-**Problem:** Server won't start
-
-```bash
-# Check if port 5000 is in use
-netstat -ano | findstr :5000 # Windows
-lsof -ti:5000 # Mac/Linux
-
-# Kill process or use different port
-export PORT=5001
-python main.py
-```
-
-**Problem:** Database errors
-
-```bash
-# Reinitialize database
-rm instance/credentials.db
-python -c "from app.models import init_database; from app.app import app; init_database(app)"
-```
-
-**Problem:** Verification fails
-
-```bash
-# Check all components
-python << 'EOF'
-from core.blockchain import SimpleBlockchain
-from core.crypto_utils import CryptoManager
-from core.ipfs_client import IPFSClient
-
-# Test blockchain
-bc = SimpleBlockchain()
-print(f"Blockchain valid: {bc.is_chain_valid()}")
-
-# Test crypto
-crypto = CryptoManager()
-test = crypto.sign_data("test")
-print(f"Signature works: {crypto.verify_signature('test', test)}")
-
-# Test IPFS
-ipfs = IPFSClient()
-print(f"IPFS connected: {ipfs.is_connected()}")
-EOF
-```
-
-For detailed troubleshooting, see `TROUBLESHOOTING.md`.
-
-***
-
-## Advanced Topics
-
-### Topic 1: Scaling the System
-
-**Current:** Single-server SQLite
-
-**Enterprise Scale:**
-
-```
-
- Load Balancer (Nginx)
-
-
-
- Flask Flask (Multiple instances)
- Server 1 Server 2
-
-
-
- PostgreSQL Cluster
- (Master + Replicas)
-
-```
-
-
-### Topic 2: Implementing DIDs
-
-**Decentralized Identifiers:**
-
-```json
-{
- "@context": "https://www.w3.org/ns/did/v1",
- "id": "did:example:university123",
- "authentication": [{
- "id": "did:example:university123#keys-1",
- "type": "RsaVerificationKey2018",
- "controller": "did:example:university123",
- "publicKeyPem": "-----BEGIN PUBLIC KEY-----..."
- }]
-}
-```
-
-
-### Topic 3: Smart Contract Integration
-
-**Ethereum/Polygon Integration:**
-
-```solidity
-contract CredentialRegistry {
- mapping(bytes32 => Credential) public credentials;
-
- struct Credential {
- string ipfsCID;
- address issuer;
- uint256 timestamp;
- bool revoked;
- }
-
- function issue(bytes32 credID, string memory cid) public {
- credentials[credID] = Credential(cid, msg.sender, block.timestamp, false);
- }
-
- function verify(bytes32 credID) public view returns (bool) {
- return !credentials[credID].revoked;
- }
-}
-```
-
-
-***
-
-## Best Practices
-
-### 1. Code Organization
-
-- Keep concerns separated (MVC pattern)
-- Use meaningful variable names
-- Comment complex logic
-- Follow PEP 8 style guide
-
-
-### 2. Security
-
-- Never commit secrets to Git
-- Use environment variables
-- Implement rate limiting
-- Enable HTTPS in production
-- Regular dependency updates
-
-
-### 3. Testing
-
-- Write unit tests for all components
-- Integration tests for workflows
-- Test edge cases
-- Maintain test coverage > 80%
-
-
-### 4. Documentation
-
-- Keep README updated
-- Document API endpoints
-- Inline code comments
-- User guides for each role
-
-
-### 5. Performance
-
-- Cache frequently accessed data
-- Optimize database queries
-- Use async for IPFS operations
-- Monitor system metrics
-
-***
-
-## Conclusion
-
-### What You've Learned
-
- **Blockchain Technology**
-
-- Block structure and hashing
-- Proof-of-work consensus
-- Chain validation and integrity
-
- **Cryptography**
-
-- RSA-2048 digital signatures
-- SHA-256 hashing
-- Key management
-
- **Distributed Storage**
-
-- IPFS content addressing
-- Decentralized data storage
-- Fallback strategies
-
- **Web Development**
-
-- Flask application architecture
-- Role-based access control
-- RESTful API design
-
- **Real-World Application**
-
-- Academic credential verification
-- Privacy-preserving disclosure
-- Production deployment
-
-
-### Next Steps
-
-1. **Extend the System:**
- - Add mobile app
- - Implement QR codes
- - Multi-language support
-2. **Integrate with Enterprise:**
- - Connect to university systems
- - Employer verification portals
- - Government credential registries
-3. **Research Advanced Topics:**
- - Zero-knowledge proofs (zk-SNARKs)
- - Decentralized identifiers (DIDs)
- - Blockchain interoperability
-
-### Resources
-
-- **W3C Verifiable Credentials:** https://www.w3.org/TR/vc-data-model/
-- **IPFS Documentation:** https://docs.ipfs.tech/
-- **Flask Documentation:** https://flask.palletsprojects.com/
-- **Cryptography Library:** https://cryptography.io/
-
-
-### Support
-
-**Development Team:**
-
-- [@udaycodespace](https://github.com/udaycodespace) Backend \& Blockchain
-- [@shashikiran47](https://github.com/shashikiran47) Frontend \& IPFS
-- [@tejavarshith](https://github.com/tejavarshith) Testing \& Documentation
-
-**Community:**
-
-- GitHub Issues: Report bugs
-- GitHub Discussions: Ask questions
-- Pull Requests: Contribute code
-
-***
-
-
-
-**Congratulations on completing the tutorial!**
-
-**G. Pulla Reddy Engineering College (Autonomous)**
-
-**Version 2.1 | March 2026**
-
-***
-
-*From Academic Project to Production System*
-
-
-
-***
-
-> [!NOTE]
-> ** TUTORIAL STATUS: UPDATED**
->
-> **Architecture Version:** 2.1.0 (Elite Milestone)
->
-> **Current Edited Date:** `2026-03-08`
-
-
diff --git a/pyproject.toml b/pyproject.toml
index f427e2b..ea58778 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "credify"
-version = "2.1.0"
+version = "2.0.0"
description = "Elite Private Blockchain Verifiable Credential System with ZKP & IPFS"
authors = ["Somapuram Uday", "Shashi Kiran", "Teja Varshith"]
readme = "README.md"
diff --git a/tests/test_blockchain.py b/tests/test_blockchain.py
index 13a4b8e..112e05f 100644
--- a/tests/test_blockchain.py
+++ b/tests/test_blockchain.py
@@ -86,3 +86,77 @@ def test_tamper_detection(blockchain):
blockchain.chain[1].data = {'data': 'TAMPERED_DATA'}
assert blockchain.is_chain_valid() is False
+
+
+def test_deterministic_leader_formula(blockchain):
+ """Leader selection uses height % total_nodes over deterministic ring."""
+ blockchain.node_address = "http://node1:5000"
+ blockchain.node_id = "node1"
+ blockchain.nodes = {"node2:5000", "node3:5000"}
+
+ # Height = current chain length. After genesis, length is 1.
+ leader_meta = blockchain.get_deterministic_leader(len(blockchain.chain))
+ ring = leader_meta["ring"]
+ expected_index = len(blockchain.chain) % len(ring)
+
+ assert leader_meta["leader_index"] == expected_index
+ assert leader_meta["leader"] == ring[expected_index]
+
+
+def test_non_leader_block_creation_rejected(blockchain):
+ """Only deterministic leader can create local blocks."""
+ blockchain.node_address = "http://node1:5000"
+ blockchain.node_id = "node1"
+ blockchain.nodes = {"node2:5000", "node3:5000"}
+
+ # With ring [node1,node2,node3] and height=1, leader is node2.
+ with pytest.raises(PermissionError, match="Deterministic leader gate rejected"):
+ blockchain.add_block({'credential_id': 'LEADER-GATE-TEST'}, signed_by="admin")
+
+
+def test_block_contains_proposed_by(blockchain):
+ """New blocks should carry proposed_by from current node id."""
+ blockchain.node_id = "node-alpha"
+ blockchain.node_address = ""
+ blockchain.nodes = set()
+ blockchain.set_node_validators({"node-alpha", "standalone"})
+
+ block = blockchain.add_block({'check': 'proposer-field'}, signed_by="admin")
+ as_dict = block.to_dict()
+
+ assert as_dict.get('proposed_by') == 'node-alpha'
+
+
+def test_new_block_is_finalized(blockchain):
+ """Accepted local blocks must carry FINALIZED status."""
+ blockchain.node_id = "standalone"
+ blockchain.node_address = ""
+ blockchain.nodes = set()
+ blockchain.set_node_validators({"standalone"})
+
+ block = blockchain.add_block({'finality': 'local'}, signed_by="admin")
+ assert block.to_dict().get('status') == 'FINALIZED'
+
+
+def test_hash_input_determinism_for_dict_key_order(blockchain):
+ """Canonicalization should normalize dict key order before hashing."""
+ from core.blockchain import Block
+
+ payload_a = {'b': 2, 'a': 1, 'nested': {'z': 9, 'm': 5}}
+ payload_b = {'nested': {'m': 5, 'z': 9}, 'a': 1, 'b': 2}
+
+ canonical_a = blockchain._canonicalize_data(payload_a)
+ canonical_b = blockchain._canonicalize_data(payload_b)
+
+ block_a = Block(7, canonical_a, 'prev_hash_value', signed_by='admin', proposed_by='node1')
+ block_b = Block(7, canonical_b, 'prev_hash_value', signed_by='admin', proposed_by='node1')
+
+ # Align non-deterministic runtime fields for a true same-input comparison.
+ block_a.timestamp = '2026-03-25T10:00:00'
+ block_b.timestamp = '2026-03-25T10:00:00'
+ block_a.nonce = 0
+ block_b.nonce = 0
+ block_a.merkle_root = block_a.calculate_merkle_root()
+ block_b.merkle_root = block_b.calculate_merkle_root()
+
+ assert block_a.calculate_hash() == block_b.calculate_hash()
diff --git a/tests/test_peer_sync.py b/tests/test_peer_sync.py
index 1fb4dab..54dfd92 100644
--- a/tests/test_peer_sync.py
+++ b/tests/test_peer_sync.py
@@ -90,3 +90,64 @@ def test_receive_block_valid_workflow(client, crypto_manager):
assert response.status_code == 200
assert json.loads(response.data)['success'] is True
assert len(app_blockchain.chain) == index + 1
+ assert app_blockchain.chain[-1].to_dict().get('status') == 'FINALIZED'
+
+
+def test_receive_block_duplicate_ignored(client, crypto_manager):
+ """Duplicate peer blocks should be ignored idempotently."""
+ from app.app import blockchain as app_blockchain
+ from core.blockchain import Block
+
+ index = len(app_blockchain.chain)
+ prev_hash = app_blockchain.get_latest_block().hash
+ duplicate_candidate = Block(index, {"idempotent": True}, prev_hash, signed_by="admin")
+ duplicate_candidate.mine_block(0)
+ duplicate_candidate.signature = crypto_manager.sign_data(duplicate_candidate.hash)
+
+ first = client.post(
+ '/api/node/receive_block',
+ data=json.dumps(duplicate_candidate.to_dict()),
+ content_type='application/json'
+ )
+ assert first.status_code == 200
+ assert len(app_blockchain.chain) == index + 1
+
+ second = client.post(
+ '/api/node/receive_block',
+ data=json.dumps(duplicate_candidate.to_dict()),
+ content_type='application/json'
+ )
+ assert second.status_code == 200
+ assert json.loads(second.data)['message'] in ['Duplicate block ignored', 'Outdated block ignored']
+ assert len(app_blockchain.chain) == index + 1
+
+
+def test_broadcast_block_skips_sender_and_self(blockchain, monkeypatch):
+ """Gossip relay should not post back to sender or self node address."""
+ sent_urls = []
+
+ class DummyResponse:
+ status_code = 200
+
+ def fake_post(url, json=None, headers=None, timeout=0):
+ sent_urls.append(url)
+ return DummyResponse()
+
+ import requests
+
+ monkeypatch.setattr(requests, "post", fake_post)
+
+ blockchain.nodes = set()
+ blockchain.node_address = "http://node1:5000"
+ blockchain.node_id = "node1"
+
+ block = blockchain.add_block({"relay": "check"}, signed_by="admin")
+ sent_urls.clear()
+
+ blockchain.nodes = {"node1:5000", "node2:5000", "node3:5000"}
+
+ blockchain.broadcast_block(block, source_node="http://node2:5000", origin_node="nodeX")
+
+ assert "http://node2:5000/api/node/receive_block" not in sent_urls
+ assert "http://node1:5000/api/node/receive_block" not in sent_urls
+ assert "http://node3:5000/api/node/receive_block" in sent_urls