Decentralized IoC sharing on Ethereum, with on-chain reputation scoring for the trust problem threat intel has always had.
Threat intelligence sharing has a coordination problem. When a blue team analyst discovers a malicious IP or a new C2 domain, the obvious next step is to share it — but with whom, and under what trust model? Centralized threat feeds (VirusTotal, OTX, commercial ISAC platforms) gate access, impose editorial control, and create single points of failure. Peer-to-peer sharing through mailing lists and Slack channels has no accountability layer: anyone can submit garbage, and there is no persistent record of who contributed what or how reliable their past contributions were.
Blockchain gives you both properties in the same system. A shared ledger records who reported what indicator, when, and what the community consensus was on its validity. Because writes are append-only and timestamped by the network, the audit trail is tamper-evident. And because every reporter's address accumulates a reputation score based on community validation, there is a persistent, queryable measure of source reliability — something that STIX/TAXII threat feeds have no native mechanism for.
CTIChain is a working proof of concept for this model. It is not a production threat intel platform. It is an engineering exercise in applying blockchain's integrity guarantees to a real security problem — one where the trust model actually matters, not just the buzzword.
flowchart LR
subgraph Frontend
A[React + Vite]
B[MetaMask Wallet]
end
subgraph Blockchain
C[Ethers.js v6]
D["ThreatIntelligence.sol\n(Hardhat Local / Sepolia)"]
E[Event Logs]
end
subgraph On-Chain Storage
F["IoC Record:\nhash + indicator + severity"]
G["Reporter Address →\nReputation Score"]
H["Submission Events →\nAudit Log"]
end
A -->|user action| B
B -->|signed tx| C
C -->|submitThreat / vote| D
D --> F
D --> G
D -->|ThreatSubmitted\nThreatVoted\nReputationUpdated| E
E -->|getAllThreats query| A
F --> H
Data flow for a submission: The analyst fills in an indicator (IP, domain, or file hash) and a description in the React frontend. The frontend computes keccak256(abi.encodePacked(indicator, description)) client-side and passes it as metadataRef. The smart contract independently recomputes the same hash and rejects the transaction if there is a mismatch — this is an on-chain integrity check, not a privacy mechanism. The indicator text, description, severity, reporter address, and block timestamp are all stored on-chain.
The entire system runs on a single Solidity contract: ThreatIntelligence.sol.
Each threat record is a struct containing: a sequential ID, the submitter's address, the indicator type (IP, DOMAIN, HASH), the raw indicator string, a free-text description, a severity score (1–10), a block timestamp, a metadataRef hash (keccak256 of indicator + description), and upvote/downvote counters.
The raw indicator is stored on-chain rather than just a hash. This is a deliberate trade-off: it means any node operator can read submitted IoCs directly from contract storage, which is useful for a shared threat feed but means this contract should not be used for sensitive or classified indicators. The metadataRef hash serves as a tamper-detection mechanism — it proves the indicator and description were not modified between client-side preparation and on-chain storage.
A separate indicatorExists mapping enforces uniqueness: the same indicator string cannot be submitted twice, preventing duplicate entries at the contract level.
The reputation model is intentionally simple — linear, not quadratic:
- +10 REP for each threat submission
- +5 REP for each upvote received from another address on your submission
- No penalty for downvotes (downvotes are recorded but do not decrease reputation)
Linear scaling was chosen because this is a v1 proof of concept. Quadratic or stake-weighted models introduce complexity around economic incentives that would require game-theoretic analysis beyond the project's scope. The current model is sufficient to demonstrate the concept: prolific, validated contributors accumulate higher scores than drive-by submitters.
The no-penalty design is a known limitation. In a production system, downvotes should reduce reputation to disincentivize low-quality submissions. This is acknowledged in the roadmap.
The contract is fully permissionless — there is no admin role, no owner, no privileged address. Any Ethereum address can:
- Submit a threat (
submitThreat) - Vote on any threat they did not submit (
vote) - Register an organization name (
registerOrganization) - Read all data (
getAllThreats,getReputation,getOrganization)
Self-voting is prevented: the contract requires threats[_id].submitter != msg.sender. Each address can vote only once per threat, enforced by the hasVoted double mapping. Organization registration is one-time per address.
There is no pause mechanism, no upgrade proxy, and no kill switch. Once deployed, the contract is immutable and autonomous.
submitThreatis the most expensive call: it writes a new struct to storage, updates theindicatorExistsmapping, incrementsreputation, and emits two eventsvotewrites tohasVoted, updates a vote counter, conditionally updates reputation, and emits one or two eventsgetAllThreatsis aviewfunction that allocates a memory array of sizethreatCount— free for off-chain calls, but gas cost scales linearly with total submissions if called within another contractregisterOrganizationwrites a struct once and emits an event
The contract does not use OpenZeppelin's ReentrancyGuard. However, reentrancy is not a practical risk in this contract because:
- There are no external calls to untrusted contracts — no
call,delegatecall,transfer, orsend - No Ether is sent or received — the contract has no
receive()orfallback()function - All state mutations happen before any event emissions (check-effects-interactions pattern)
Without an external call to an untrusted address, there is no reentry vector.
- Front-running of duplicate reports: An attacker watching the mempool can see a pending
submitThreattransaction and front-run it with the same indicator, stealing credit. TheindicatorExistsuniqueness check means the original reporter's transaction would then revert. - Sybil resistance: Reputation is per-address with no identity verification. An adversary can create unlimited addresses to self-upvote submissions via separate wallets, inflating reputation at the cost of gas fees only.
- No downvote penalty: Downvotes do not reduce reputation, so there is no economic disincentive for submitting low-quality indicators.
- Plaintext indicators on-chain: Raw IoC values are publicly readable in contract storage. This is fine for community threat sharing but inappropriate for classified or embargoed intelligence.
getAllThreatsscaling: The function allocates a memory array proportional to total submissions. At very high submission volumes, this could become expensive for on-chain consumers (off-chain reads are unaffected).
| Category | Status | Notes |
|---|---|---|
| Reentrancy | Not applicable | No external calls to untrusted contracts; no Ether transfers |
| Integer overflow | Protected | Solidity 0.8.19 compiler-native checked arithmetic |
| Access control | Permissionless by design | No admin role; self-vote prevention enforced on-chain |
| Front-running | Acknowledged, unmitigated in v1 | See roadmap for commit-reveal scheme |
| Denial of service via gas exhaustion | No unbounded loops in state-changing functions | getAllThreats iterates threatCount but is view-only |
| Duplicate submission | Protected | indicatorExists mapping rejects duplicate indicator strings |
| Hash integrity | Enforced | metadataRef verified against keccak256(abi.encodePacked(indicator, description)) on every submission |
A blue team analyst at an enterprise SOC identifies a malicious IP address — say, a C2 callback observed in firewall logs during incident triage. The analyst wants to share this indicator with the broader community so other organizations can block it proactively.
The analyst opens CTIChain in their browser, connects their MetaMask wallet, and fills in the submission form: indicator type (IP), the raw IP address, a free-text description of the observed behavior ("C2 callback on port 443, observed during ransomware IR, drops Cobalt Strike beacon"), and a severity rating. The frontend computes the keccak256 hash of the indicator and description, then sends the transaction to the contract. MetaMask prompts for confirmation; the analyst signs and the transaction is broadcast to the network.
The contract validates the hash, checks for duplicate indicators, stores the full record on-chain, awards the submitter +10 reputation, and emits a ThreatSubmitted event. The event is indexed by threat ID and submitter address, creating a queryable audit trail. The indicator is now immutably recorded with the reporter's Ethereum address and the block timestamp as provenance.
Other analysts querying the threat feed see the new submission. If they independently validate the indicator — finding the same IP in their own logs, or correlating it with other intelligence — they can upvote the report. Each upvote awards +5 reputation to the original submitter and is recorded on-chain. Over time, analysts who consistently submit validated, high-quality indicators accumulate higher reputation scores, creating a decentralized trust signal that requires no central authority to maintain.
| Layer | Technology |
|---|---|
| Smart contracts | Solidity 0.8.19, Hardhat v2 |
| Frontend | React 19, Vite 8, Ethers.js v6 |
| Wallet integration | MetaMask (EIP-1193 provider) |
| Network | Hardhat local (31337); Sepolia testnet for public deployment |
| Cryptography | keccak256 for IoC fingerprinting and integrity verification |
| Deployment pipeline | Custom deploy script auto-writes ABI and contract address to frontend |
- Node.js 22+
- MetaMask browser extension
- A Sepolia RPC endpoint (Alchemy or Infura) if deploying to testnet
git clone https://github.com/<your-username>/CTIChain-Dapp.git
cd CTIChain-Dapp
npm install
cd frontend && npm install && cd ..npx hardhat compileStart a local Hardhat node in one terminal:
npx hardhat nodeDeploy in a second terminal:
npx hardhat run scripts/deploy.cjs --network localhostThe deploy script automatically writes the contract address and ABI to frontend/src/utils/, so the frontend picks up the deployment with zero manual configuration.
- Open MetaMask and add a custom network:
- Network Name: Hardhat Local
- RPC URL:
http://127.0.0.1:8545 - Chain ID: 31337
- Currency Symbol: ETH
- Import one of the private keys printed by
npx hardhat node
Add a Sepolia network configuration to hardhat.config.cjs:
sepolia: {
url: process.env.SEPOLIA_RPC_URL, // e.g. https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
accounts: [process.env.DEPLOYER_KEY], // private key of a funded Sepolia wallet
}Then deploy:
npx hardhat run scripts/deploy.cjs --network sepoliaThe deploy script is network-agnostic — it works for Sepolia without modification. It will write the deployed address and ABI to the frontend utils directory.
cd frontend
npm run devThe app runs at http://localhost:5173.
| Network | Contract Address | Deploy Block | Explorer Link |
|---|---|---|---|
| Hardhat Local | Written to frontend/src/utils/constants.js on each deploy |
N/A | N/A |
Sepolia deployment is configured and ready but not yet live. Once deployed, the contract address will be recorded here with an Etherscan link.
The hardest part of this project was reasoning about trust boundaries in a system where the blockchain guarantees integrity of writes but not truth of content. Blockchain ensures that if address 0xABC submitted indicator X at block N, that fact is immutable. But it says nothing about whether X is actually malicious, whether 0xABC is a credible analyst, or whether the submission was made in good faith. The reputation system is an attempt to bridge that gap — but it only works if the cost of gaming the system (creating sybil addresses, paying gas for fake submissions) exceeds the benefit. On a testnet with free gas, it does not. This is the fundamental tension between "decentralized" as a security property (tamper-evidence, availability) and "decentralized" as a governance property (who decides what is true).
The sybil problem was the most instructive. On-chain reputation is meaningless if an adversary can create infinite addresses and upvote their own content. Every serious decentralized identity system eventually converges on some form of proof of personhood (Worldcoin's iris scans, Gitcoin Passport's social graph, BrightID's verification parties) or economic staking (you lose money if your contributions are later disproved). CTIChain v1 has neither, which is an honest acknowledgment of where the hard problems actually are. Solving the smart contract engineering was straightforward. Solving the trust model is an open research problem.
Building this also clarified the distinction between blockchain as a technical architecture and blockchain as a security control. The immutability guarantee is real and useful — it gives you a non-repudiable audit log of who shared what. But immutability without access control means anyone can write to that log, and immutability without validation means garbage is permanent. In a threat intelligence context, both of those properties have direct security implications. The lesson is that blockchain is a building block, not a solution: you still need off-chain processes (peer review, identity verification, STIX/TAXII standardization) to make the system trustworthy.
- Sybil resistance: Integrate proof-of-humanity verification (Worldcoin, Gitcoin Passport) or require economic staking to gate submission and voting privileges
- Off-chain IoC storage: Move raw indicator content to IPFS with on-chain hash commitments, reducing gas costs and enabling storage of larger threat reports
- Front-running mitigation: Implement a commit-reveal scheme where submitters first commit a hash of their report, then reveal the content in a second transaction after the commit is mined
- STIX/TAXII integration: Standardize IoC format to align with existing threat intelligence exchange protocols, enabling interoperability with enterprise SIEM/SOAR platforms
- L2 deployment: Deploy to an Ethereum L2 (Arbitrum, Base) to reduce gas costs from dollars to fractions of a cent per submission
- Downvote penalties: Implement reputation reduction on downvotes with a threshold mechanism to prevent griefing
CTIChain is a research prototype built for an academic course. It is deployed on test networks with no real economic value at stake. The IoC data submitted during development and testing is synthetic. This tool should not be used as a production threat intelligence source, and the reputation scores it produces should not be treated as authoritative assessments of analyst credibility. If you adapt this code for real-world use, you accept responsibility for implementing the access controls, identity verification, and operational security measures appropriate to your threat model.
Umar Murtaza CS4049 Blockchain & Cryptocurrency — FAST-NUCES
LinkedIn · GitHub · umarmurtaza605050@gmail.com
MIT