Blockchain added

This commit is contained in:
2026-04-22 11:04:59 +01:00
parent e0dc9ba2ba
commit cd11e76c07
23 changed files with 10984 additions and 61 deletions

View File

@@ -0,0 +1,206 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title DocumentRegistry
* @author LexiChain BFSI Platform
* @notice This smart contract provides on-chain proof-of-deposit for BFSI
* contract documents. It stores SHA-256 hashes of documents along
* with their registration timestamp, making submission dates
* provable, tamper-proof, and legally opposable.
*
* @dev How it works:
* 1. The platform computes a SHA-256 hash of the uploaded PDF
* 2. The hash is registered on the blockchain via registerDocument()
* 3. The block.timestamp at the time of mining becomes the proof date
* 4. Anyone can verify a document's existence via verifyDocument()
*
* No actual document content is stored on-chain — only the hash.
* This preserves privacy while providing cryptographic proof.
*/
contract DocumentRegistry {
// ═══════════════════════════════════════════════════
// DATA STRUCTURES
// ═══════════════════════════════════════════════════
/**
* @notice Represents a registered document's on-chain record
* @param timestamp When the document was registered (block.timestamp)
* @param depositor The address that registered the document
* @param exists Whether this record is valid
*/
struct DocumentRecord {
uint256 timestamp;
address depositor;
bool exists;
}
// ═══════════════════════════════════════════════════
// STATE VARIABLES
// ═══════════════════════════════════════════════════
/// @notice Maps document hash → registration record
mapping(bytes32 => DocumentRecord) private documents;
/// @notice Maps depositor address → list of document hashes they registered
mapping(address => bytes32[]) private depositorDocuments;
/// @notice Total number of documents registered
uint256 public totalDocuments;
/// @notice Contract owner (the platform backend wallet)
address public owner;
// ═══════════════════════════════════════════════════
// EVENTS
// ═══════════════════════════════════════════════════
/**
* @notice Emitted when a new document is registered on-chain
* @param docHash The SHA-256 hash of the document
* @param timestamp The block timestamp at registration
* @param depositor The address that registered the document
*/
event DocumentRegistered(
bytes32 indexed docHash,
uint256 timestamp,
address indexed depositor
);
/**
* @notice Emitted when a document is verified
* @param docHash The document hash that was checked
* @param exists Whether the document was found on-chain
* @param verifier The address that performed the verification
*/
event DocumentVerified(
bytes32 indexed docHash,
bool exists,
address indexed verifier
);
// ═══════════════════════════════════════════════════
// MODIFIERS
// ═══════════════════════════════════════════════════
/// @notice Restricts function to contract owner
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
// ═══════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════
constructor() {
owner = msg.sender;
}
// ═══════════════════════════════════════════════════
// CORE FUNCTIONS
// ═══════════════════════════════════════════════════
/**
* @notice Register a document hash on the blockchain
* @dev Creates an immutable record with the current block timestamp.
* Reverts if the same hash was already registered (no duplicates).
* @param _docHash The SHA-256 hash of the document (bytes32)
*/
function registerDocument(
bytes32 _docHash
) external onlyOwner {
// Prevent duplicate registrations
require(
!documents[_docHash].exists,
"Document already registered on-chain"
);
// Store the document record
documents[_docHash] = DocumentRecord({
timestamp: block.timestamp,
depositor: msg.sender,
exists: true
});
// Track documents per depositor
depositorDocuments[msg.sender].push(_docHash);
// Increment counter
totalDocuments++;
// Emit event for off-chain indexing
emit DocumentRegistered(
_docHash,
block.timestamp,
msg.sender
);
}
/**
* @notice Verify if a document exists on-chain and get its details
* @param _docHash The SHA-256 hash of the document to verify
* @return exists Whether the document is registered
* @return timestamp When it was registered (0 if not found)
* @return depositor Who registered it (address(0) if not found)
*/
function verifyDocument(
bytes32 _docHash
)
external
view
returns (
bool exists,
uint256 timestamp,
address depositor
)
{
DocumentRecord memory record = documents[_docHash];
return (
record.exists,
record.timestamp,
record.depositor
);
}
/**
* @notice Get the timestamp for a specific document hash
* @param _docHash The document hash to look up
* @return The registration timestamp (0 if not registered)
*/
function getTimestamp(bytes32 _docHash) external view returns (uint256) {
return documents[_docHash].timestamp;
}
/**
* @notice Get all document hashes registered by a specific address
* @param _depositor The address to query
* @return Array of document hashes
*/
function getDocumentsByDepositor(
address _depositor
) external view returns (bytes32[] memory) {
return depositorDocuments[_depositor];
}
/**
* @notice Get the number of documents registered by a specific address
* @param _depositor The address to query
* @return Number of documents
*/
function getDocumentCount(
address _depositor
) external view returns (uint256) {
return depositorDocuments[_depositor].length;
}
/**
* @notice Transfer ownership of the contract
* @dev Only the current owner can transfer ownership
* @param _newOwner The address of the new owner
*/
function transferOwnership(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "New owner cannot be zero address");
owner = _newOwner;
}
}

View File

@@ -0,0 +1,56 @@
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
// ─────────────────────────────────────────────────
// Hardhat Configuration
// ─────────────────────────────────────────────────
// This file configures the Solidity compiler and
// network settings for our smart contract.
//
// Networks:
// - hardhat (default): In-memory local blockchain, free & instant
// - localhost: Persistent local node via `npx hardhat node`
// - sepolia: Ethereum testnet for demo/presentation
// ─────────────────────────────────────────────────
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
// Local persistent node (run `npx hardhat node` first)
localhost: {
url: "http://127.0.0.1:8545",
},
// Ethereum Sepolia testnet (free, for demo/jury presentation)
// Requires SEPOLIA_RPC_URL and DEPLOYER_PRIVATE_KEY in env
...(process.env.SEPOLIA_RPC_URL
? {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: process.env.DEPLOYER_PRIVATE_KEY
? [process.env.DEPLOYER_PRIVATE_KEY]
: [],
chainId: 11155111,
},
}
: {}),
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts",
},
};
export default config;

8094
blockchain/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
blockchain/package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "lexichain-blockchain",
"version": "1.0.0",
"description": "LexiChain Document Registry - Solidity Smart Contract",
"scripts": {
"compile": "hardhat compile",
"test": "hardhat test",
"node": "hardhat node",
"deploy:local": "hardhat run scripts/deploy.ts --network localhost",
"deploy:sepolia": "hardhat run scripts/deploy.ts --network sepolia"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"hardhat": "^2.22.0",
"typescript": "^5.0.0",
"ts-node": "^10.9.0",
"@types/node": "^20.0.0"
}
}

View File

@@ -0,0 +1,46 @@
import { ethers } from "hardhat";
/**
* Deployment Script for DocumentRegistry
*
* Usage:
* Local: npx hardhat run scripts/deploy.ts --network localhost
* Sepolia: npx hardhat run scripts/deploy.ts --network sepolia
*
* After deployment, copy the contract address into your .env file:
* BLOCKCHAIN_CONTRACT_ADDRESS=0x...
*/
async function main() {
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("🔗 Deploying DocumentRegistry...");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
// Get the deployer account
const [deployer] = await ethers.getSigners();
console.log(`📍 Deployer address: ${deployer.address}`);
const balance = await ethers.provider.getBalance(deployer.address);
console.log(`💰 Deployer balance: ${ethers.formatEther(balance)} ETH`);
// Deploy the contract
const DocumentRegistryFactory = await ethers.getContractFactory("DocumentRegistry");
const registry = await DocumentRegistryFactory.deploy();
await registry.waitForDeployment();
const address = await registry.getAddress();
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log(`✅ DocumentRegistry deployed to: ${address}`);
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("");
console.log("📋 Next steps:");
console.log(` 1. Add to your .env file:`);
console.log(` BLOCKCHAIN_CONTRACT_ADDRESS=${address}`);
console.log(` 2. The contract is ready to register documents!`);
console.log("");
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

View File

@@ -0,0 +1,170 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import { DocumentRegistry } from "../typechain-types";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
/**
* DocumentRegistry Smart Contract Tests
*
* These tests verify all core functionality:
* 1. Document registration with timestamp
* 2. Document verification
* 3. Duplicate prevention
* 4. Non-existent document handling
* 5. Depositor document tracking
* 6. Ownership controls
*/
describe("DocumentRegistry", function () {
let registry: DocumentRegistry;
let owner: SignerWithAddress;
let user1: SignerWithAddress;
let user2: SignerWithAddress;
// Sample document hashes (simulating SHA-256 hashes)
const docHash1 = ethers.keccak256(ethers.toUtf8Bytes("contract-insurance-auto-2024.pdf"));
const docHash2 = ethers.keccak256(ethers.toUtf8Bytes("contract-home-loan-2024.pdf"));
const docHash3 = ethers.keccak256(ethers.toUtf8Bytes("contract-health-insurance.pdf"));
beforeEach(async function () {
// Get test accounts (Hardhat provides 20 free accounts)
[owner, user1, user2] = await ethers.getSigners();
// Deploy new contract instance before each test
const DocumentRegistryFactory = await ethers.getContractFactory("DocumentRegistry");
registry = await DocumentRegistryFactory.deploy();
await registry.waitForDeployment();
});
// ═══════════════════════════════════════════════════
// DEPLOYMENT TESTS
// ═══════════════════════════════════════════════════
describe("Deployment", function () {
it("should set the deployer as owner", async function () {
expect(await registry.owner()).to.equal(owner.address);
});
it("should start with zero documents", async function () {
expect(await registry.totalDocuments()).to.equal(0);
});
});
// ═══════════════════════════════════════════════════
// REGISTRATION TESTS
// ═══════════════════════════════════════════════════
describe("Document Registration", function () {
it("should register a document and emit event", async function () {
const tx = await registry.registerDocument(docHash1);
const receipt = await tx.wait();
// Verify event was emitted
await expect(tx)
.to.emit(registry, "DocumentRegistered")
.withArgs(docHash1, (await ethers.provider.getBlock(receipt!.blockNumber))!.timestamp, owner.address);
});
it("should store correct timestamp", async function () {
const tx = await registry.registerDocument(docHash1);
const receipt = await tx.wait();
const block = await ethers.provider.getBlock(receipt!.blockNumber);
const timestamp = await registry.getTimestamp(docHash1);
expect(timestamp).to.equal(block!.timestamp);
});
it("should increment totalDocuments counter", async function () {
await registry.registerDocument(docHash1);
expect(await registry.totalDocuments()).to.equal(1);
await registry.registerDocument(docHash2);
expect(await registry.totalDocuments()).to.equal(2);
});
it("should prevent duplicate registration (same hash)", async function () {
await registry.registerDocument(docHash1);
await expect(
registry.registerDocument(docHash1)
).to.be.revertedWith("Document already registered on-chain");
});
it("should prevent non-owners from registering documents", async function () {
await expect(
registry.connect(user1).registerDocument(docHash1)
).to.be.revertedWith("Only owner can call this function");
});
});
// ═══════════════════════════════════════════════════
// VERIFICATION TESTS
// ═══════════════════════════════════════════════════
describe("Document Verification", function () {
it("should verify a registered document", async function () {
await registry.registerDocument(docHash1);
const [exists, timestamp, depositor] = await registry.verifyDocument(docHash1);
expect(exists).to.be.true;
expect(timestamp).to.be.greaterThan(0);
expect(depositor).to.equal(owner.address);
});
it("should return false for unregistered document", async function () {
const fakeHash = ethers.keccak256(ethers.toUtf8Bytes("non-existent.pdf"));
const [exists, timestamp, depositor] = await registry.verifyDocument(fakeHash);
expect(exists).to.be.false;
expect(timestamp).to.equal(0);
expect(depositor).to.equal(ethers.ZeroAddress);
});
});
// ═══════════════════════════════════════════════════
// DEPOSITOR TRACKING TESTS
// ═══════════════════════════════════════════════════
describe("Depositor Tracking", function () {
it("should track all documents by a depositor", async function () {
await registry.registerDocument(docHash1);
await registry.registerDocument(docHash2);
const docs = await registry.getDocumentsByDepositor(owner.address);
expect(docs.length).to.equal(2);
expect(docs[0]).to.equal(docHash1);
expect(docs[1]).to.equal(docHash2);
});
it("should return correct document count", async function () {
await registry.registerDocument(docHash1);
await registry.registerDocument(docHash2);
await registry.registerDocument(docHash3);
expect(await registry.getDocumentCount(owner.address)).to.equal(3);
expect(await registry.getDocumentCount(user2.address)).to.equal(0);
});
});
// ═══════════════════════════════════════════════════
// OWNERSHIP TESTS
// ═══════════════════════════════════════════════════
describe("Ownership", function () {
it("should transfer ownership", async function () {
await registry.transferOwnership(user1.address);
expect(await registry.owner()).to.equal(user1.address);
});
it("should prevent non-owner from transferring ownership", async function () {
await expect(
registry.connect(user1).transferOwnership(user2.address)
).to.be.revertedWith("Only owner can call this function");
});
it("should prevent transfer to zero address", async function () {
await expect(
registry.transferOwnership(ethers.ZeroAddress)
).to.be.revertedWith("New owner cannot be zero address");
});
});
});

11
blockchain/tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}