Blockchain added
This commit is contained in:
206
blockchain/contracts/DocumentRegistry.sol
Normal file
206
blockchain/contracts/DocumentRegistry.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
56
blockchain/hardhat.config.ts
Normal file
56
blockchain/hardhat.config.ts
Normal 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
8094
blockchain/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
blockchain/package.json
Normal file
19
blockchain/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
46
blockchain/scripts/deploy.ts
Normal file
46
blockchain/scripts/deploy.ts
Normal 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;
|
||||
});
|
||||
170
blockchain/test/DocumentRegistry.test.ts
Normal file
170
blockchain/test/DocumentRegistry.test.ts
Normal 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
11
blockchain/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user