Files
LexiChain/docs/05-smart-contract.md
2026-05-13 21:08:27 +01:00

282 lines
7.4 KiB
Markdown

# 05 — Smart Contract: DocumentRegistry
**File:** `blockchain/contracts/DocumentRegistry.sol`
**Solidity version:** `^0.8.24`
**Network:** Ethereum Sepolia Testnet (production) / Hardhat Local (development)
**Purpose:** Tamper-proof, timestamped proof-of-deposit for BFSI contract documents.
---
## Design Philosophy
> No document content is stored on-chain — only its SHA-256 hash.
This design provides:
- **Privacy**: No sensitive data ever reaches the blockchain
- **Verifiability**: Anyone with the original document can verify it existed at a specific time
- **Immutability**: Once registered, the hash and timestamp cannot be altered
- **Legal Standing**: Block timestamp provides a cryptographically provable submission date
---
## How It Works
```
1. User uploads contract PDF to LexiChain
2. Server computes SHA-256 hash of the raw file bytes
3. Server wallet signs and sends registerDocument(bytes32) transaction
4. Transaction is mined → block.timestamp becomes the proof date
5. txHash, blockNumber, blockTimestamp saved to PostgreSQL
6. User can verify anytime via verifyDocument(bytes32)
```
---
## Contract Architecture
### State Variables
| Variable | Type | Visibility | Description |
|----------|------|-----------|-------------|
| `owner` | `address` | `public` | Platform backend wallet address |
| `totalDocuments` | `uint256` | `public` | Total number of registered document hashes |
| `documents` | `mapping(bytes32 => DocumentRecord)` | `private` | Hash → registration record |
| `depositorDocuments` | `mapping(address => bytes32[])` | `private` | Address → list of hashes they registered |
### Structs
```solidity
struct DocumentRecord {
uint256 timestamp; // block.timestamp at registration
address depositor; // msg.sender (platform wallet)
bool exists; // false = not registered
}
```
### Events
```solidity
event DocumentRegistered(
bytes32 indexed docHash,
uint256 timestamp,
address indexed depositor
);
event DocumentVerified(
bytes32 indexed docHash,
bool exists,
address indexed verifier
);
```
### Modifiers
```solidity
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
```
---
## Function Reference
### `registerDocument(bytes32 _docHash)` — write
```
Access: onlyOwner
Gas: ~50,000 gas
```
Registers a document hash on-chain. Reverts if the same hash is already registered (idempotent via `requires`).
**Parameters:**
- `_docHash` — SHA-256 hash of the document as `bytes32`
**Emits:** `DocumentRegistered(docHash, block.timestamp, msg.sender)`
---
### `verifyDocument(bytes32 _docHash)` — view (free)
```
Access: Anyone
Gas: 0 (read-only call)
```
Checks if a document hash is registered and returns its record.
**Returns:**
```solidity
bool exists // true if registered
uint256 timestamp // Unix timestamp of registration (0 if not found)
address depositor // Address that registered it (address(0) if not found)
```
---
### `getTimestamp(bytes32 _docHash)` — view (free)
Returns only the registration timestamp for a given hash (0 if not registered).
---
### `getDocumentsByDepositor(address _depositor)` — view (free)
Returns all document hashes registered by a specific address.
---
### `getDocumentCount(address _depositor)` — view (free)
Returns the count of documents registered by a specific address.
---
### `transferOwnership(address _newOwner)` — write
```
Access: onlyOwner
```
Transfers contract ownership to a new address. Required if the platform wallet changes.
---
## ABI (Application Binary Interface)
```json
[
"function registerDocument(bytes32 _docHash) external",
"function verifyDocument(bytes32 _docHash) external view returns (bool exists, uint256 timestamp, address depositor)",
"function getTimestamp(bytes32 _docHash) external view returns (uint256)",
"function getDocumentsByDepositor(address _depositor) external view returns (bytes32[] memory)",
"function getDocumentCount(address _depositor) external view returns (uint256)",
"function totalDocuments() external view returns (uint256)",
"function owner() external view returns (address)",
"function transferOwnership(address _newOwner) external",
"event DocumentRegistered(bytes32 indexed docHash, uint256 timestamp, address indexed depositor)"
]
```
---
## Security Model
| Property | Implementation |
|----------|---------------|
| **Owner-only writes** | `onlyOwner` modifier prevents anyone else from registering |
| **No duplicates** | `require(!documents[hash].exists)` prevents double-registration |
| **Privacy** | Only hashes stored — no filename, no metadata, no user data |
| **Immutability** | No update/delete functions — hashes are permanent once registered |
| **Dedicated wallet** | Platform uses a separate wallet (not a personal wallet) |
---
## Deployment
### Prerequisites
1. Install Hardhat dependencies:
```bash
cd blockchain
npm install
```
2. Compile the contract:
```bash
npx hardhat compile
```
---
### Local Development (Hardhat Node)
The `npm run dev` command in the root automatically:
1. Starts a Hardhat node on `http://localhost:8545`
2. Deploys `DocumentRegistry.sol` to the local chain
3. Saves the contract address to `blockchain/.dev-deploy.json`
4. Starts the Next.js dev server with the correct env vars
To deploy manually:
```bash
# Terminal 1: Start local node
cd blockchain
npx hardhat node
# Terminal 2: Deploy
npx hardhat run scripts/deploy.ts --network localhost
```
---
### Production (Ethereum Sepolia Testnet)
**Why Sepolia?**
- 100% free (no real ETH needed)
- Stable Ethereum testnet maintained by the Ethereum Foundation
- Supported by Etherscan, Alchemy, Infura, MetaMask
- No node to run or maintain
- Transactions viewable on https://sepolia.etherscan.io
**Step 1: Get Sepolia ETH (free)**
Use one of these faucets:
- https://sepoliafaucet.com (Alchemy — requires account)
- https://faucet.quicknode.com/ethereum/sepolia
- https://www.infura.io/faucet/sepolia
A few test ETH is enough for thousands of contract registrations (~0.0005 ETH per registration).
**Step 2: Set environment variables**
```bash
# In blockchain/.env or your shell:
SEPOLIA_RPC_URL=https://rpc.sepolia.org
DEPLOYER_PRIVATE_KEY=0x<your-dedicated-wallet-private-key>
```
> ⚠️ **CRITICAL**: Never use a wallet that holds real ETH. Create a dedicated deployment wallet.
**Step 3: Deploy to Sepolia**
```bash
cd blockchain
npx hardhat run scripts/deploy.ts --network sepolia
```
The script outputs:
```
DocumentRegistry deployed to: 0x<CONTRACT_ADDRESS>
BLOCKCHAIN_CONTRACT_ADDRESS=0x<CONTRACT_ADDRESS>
```
**Step 4: Set app environment variables**
```bash
BLOCKCHAIN_NETWORK=sepolia
BLOCKCHAIN_RPC_URL=https://rpc.sepolia.org
BLOCKCHAIN_CONTRACT_ADDRESS=0x<your-deployed-address>
BLOCKCHAIN_PRIVATE_KEY=0x<your-wallet-private-key>
```
**Step 5: Verify deployment (optional)**
Visit `https://sepolia.etherscan.io/address/<CONTRACT_ADDRESS>` to confirm deployment.
---
## Running Tests
```bash
cd blockchain
npx hardhat test
```
Test file: `blockchain/test/DocumentRegistry.test.ts`
---
## Deployed Contract Information
After deploying, record your contract address here for team reference:
| Network | Contract Address | Deployer | Deployed At |
|---------|-----------------|---------|-------------|
| Sepolia | `0x...` | `0x...` | — |
| Hardhat (dev) | Auto-deployed | Account #0 | Per session |