Skip to content

Soura149/ScoreChain

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 

Repository files navigation

🧾 ScoreChain – Tamper-Proof Exam Result Publication System

image

📘 Project Description

ScoreChain is a blockchain-based smart contract built using Solidity that enables secure, transparent, and tamper-proof publication of student exam results.
Unlike traditional centralized result systems, ScoreChain leverages blockchain immutability to ensure that once a result is published, it can never be altered or deleted.

This helps educational institutions build trust and transparency, while allowing students to verify their scores publicly without revealing personal information.


🚀 What It Does

  • The exam authority (owner) deploys the smart contract and becomes the only entity authorized to publish results.
  • Each student is identified using a hashed ID (keccak256), preserving privacy while allowing verification.
  • Results once published are permanently stored on-chain and cannot be modified or tampered with.
  • Anyone can verify a result using the student's hash (computed from student ID and a private salt).

✨ Features

Owner-only publishing – Only the contract owner (exam authority) can publish results.
Immutable records – Once added, results cannot be changed or deleted.
Privacy-preserving – Uses keccak256 hashing to protect student identities.
Transparent verification – Anyone can read results on-chain using the student hash.
Gas-efficient – Designed with minimal storage and simple mappings.
Ownership transfer – Secure ownership transfer capability for administrative flexibility.


🔗 Deployed Smart Contract

Network: Celo Sepolia Testnet
Explorer: View on Blockscout


💻 Smart Contract Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title ScoreChain - Tamper-proof Exam Result Publication System
/// @author
/// @notice Owner can publish exam results that cannot be modified once published.
/// @dev Results are stored using a hashed student identifier for privacy.
contract ScoreChain {
    /// @notice Address of the contract owner (e.g., the exam authority)
    address public owner;

    /// @notice Emitted when ownership is transferred
    /// @param previousOwner The address of the previous owner
    /// @param newOwner The address of the new owner
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /// @notice Emitted when a new exam result is published
    /// @param studentHash The hashed identifier of the student
    /// @param score The numeric score of the student
    /// @param grade The grade or remark (e.g., "A+", "Pass")
    /// @param timestamp The block timestamp when published
    /// @param examId The exam identifier (e.g., "Math2025-01")
    event ResultPublished(bytes32 indexed studentHash, uint16 score, string grade, uint256 timestamp, string examId);

    /// @notice Struct to represent a student's result
    struct Result {
        uint16 score;        // numeric score (0–65535)
        string grade;        // e.g. "A", "B+", "Pass"
        string examId;       // exam identifier (e.g. "Math2025-01")
        uint256 timestamp;   // block timestamp when published
        bool exists;         // true if record already exists
    }

    /// @notice Mapping from student hash to their exam result
    mapping(bytes32 => Result) private results;

    /// @notice Restricts functions to only the contract owner
    modifier onlyOwner() {
        require(msg.sender == owner, "ScoreChain: caller is not the owner");
        _;
    }

    /// @notice Initializes the contract setting the deployer as the initial owner
    constructor() {
        owner = msg.sender;
        emit OwnershipTransferred(address(0), msg.sender);
    }

    /// @notice Transfers contract ownership to another address
    /// @param newOwner The address of the new owner
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "ScoreChain: new owner is zero address");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /// @notice Publishes a result for a student (identified by studentHash). Cannot overwrite existing result.
    /// @param studentHash keccak256 hash of the student's identifier (computed off-chain)
    /// @param score Numeric score (0–65535)
    /// @param grade Human-readable grade (e.g., "A")
    /// @param examId Exam identifier string (e.g., "Math2025-01")
    function publishResult(
        bytes32 studentHash,
        uint16 score,
        string calldata grade,
        string calldata examId
    ) external onlyOwner {
        require(!results[studentHash].exists, "ScoreChain: result already published for this student");
        results[studentHash] = Result({
            score: score,
            grade: grade,
            examId: examId,
            timestamp: block.timestamp,
            exists: true
        });

        emit ResultPublished(studentHash, score, grade, block.timestamp, examId);
    }

    /// @notice Reads a published result using the student's hash
    /// @param studentHash keccak256 hash used when publishing
    /// @return score The numeric score of the student
    /// @return grade The letter or descriptive grade
    /// @return examId The exam identifier string
    /// @return timestamp The block timestamp when the result was published
    /// @return exists Boolean indicating whether the result exists
    function getResult(bytes32 studentHash)
        external
        view
        returns (
            uint16 score,
            string memory grade,
            string memory examId,
            uint256 timestamp,
            bool exists
        )
    {
        Result storage r = results[studentHash];
        return (r.score, r.grade, r.examId, r.timestamp, r.exists);
    }

    /// @notice Computes a student's hash (for reference)
    /// @dev It’s safer to compute this off-chain so the salt remains secret
    /// @param studentId Public student identifier (e.g., roll number)
    /// @param salt Secret salt string known only to admin and student
    /// @return hash The keccak256 hash combining studentId and salt
    function computeStudentHash(string calldata studentId, string calldata salt)
        external
        pure
        returns (bytes32 hash)
    {
        hash = keccak256(abi.encodePacked(studentId, "|", salt));
    }
}

🧠 How to Use

🪙 Deploy on Remix IDE

  1. Open Remix IDE
  2. Create a new file named ScoreChain.sol
  3. Paste the above Solidity code
  4. Compile using compiler version 0.8.19
  5. Deploy the contract using Celo Sepolia Testnet

🧩 Example Workflow

Step 1: Compute Student Hash (Off-chain) Use JavaScript (with ethers.js):

ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ROLL123|secret123"));

Step 2: Publish Result (Owner only)

publishResult("0x4f9a7a2dbd80f...", 95, "A+", "Math2025-01");

Step 3: Retrieve Result (Anyone)

getResult("0x4f9a7a2dbd80f...");

🧰 Tech Stack

  • Solidity (0.8.19) – Smart contract language
  • Celo Blockchain (Testnet) – Decentralized and eco-friendly network
  • Remix IDE – For writing and deploying contracts
  • Blockscout – To explore and verify contracts on-chain

📜 License

This project is licensed under the MIT License. You are free to use, modify, and distribute it with attribution.


If you find this project useful, give it a star on GitHub!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors