Audit trail and verification
An audit trail in DigiFIR is the signed, ordered history of every action on a case, kept in the blocks of the ledger that all member institutions hold. Verification is the set of checks that let a citizen, a court or an auditor confirm that a document or a record matches that history.
What the ledger records for every action
Each committed transaction keeps:
- the signed action exactly as the person's device signed it, with their key ID, the time on their device and the action ID;
- the peers of each institution that endorsed it, with their signatures;
- the gateway identity of the institution that submitted it;
- the records it created or changed, and the hashes of any private values it wrote;
- one chaincode event named after the action type;
- the block number and transaction ID, both of which the person receives in their receipt.
Corrections never overwrite. Each record keeps its full version history, which anyone with access can read (GetHistory, or the record history page of the explorer).
Some records carry their own hash chains on top of the ledger's:
| Record | Chain |
|---|---|
| Case diary | Each entry includes the hash of the entry before it (BNSS s.192 requires a diary nothing can be removed from) |
| Arrest timeline | Events are sealed into segments every 200 events; each segment names the hash of the one before |
| Custody chain | Each hop holds the signatures of both the giver and the receiver, so a gap shows as the last holder and the time since the last hop |
| Station general diary | Each day's entries are closed at midnight; their count and digest are anchored on the ledger |
Checking a document
Anyone can check whether a police or court document is genuine without uploading it and without signing in.
- Open the verification page in the citizen app or the web portal and choose the file, or paste the text.
- The browser or phone computes the SHA-256 of the document. Only the hash leaves the device.
- The backend looks the hash up among registered documents. It answers only from blocks that passed its own verification; otherwise it asks a peer directly.
| Document | Registered by | The answer names |
|---|---|---|
| Complaint receipt | The citizen, the station or the documents service | Complaint number |
| FIR copy (BNSS s.173(2)), Zero FIR copy, NCR copy | The station or the documents service | FIR, Zero FIR or NCR number |
| Final report, arrest memo, seizure memo | The IO or the SHO | FIR number |
| Court order | The magistrate, the High Court registry or the judiciary's documents service | FIR number, or the complaint number before an FIR exists |
| Sealed laboratory report | The sealing analyst or the laboratories' documents service | Laboratory report number |
A match returns the kind of document, its number, the station or laboratory, the day it was issued, the case's current status, the kind of issuer, and the block and transaction that registered it. The answer never contains personal data. That block and transaction can be checked independently against another institution's peer.
NOTE
A hash that is not found means "not on the ledger". The backend never answers "fake": a genuine document changed by even one byte, or saved again by another program, also has a different hash. The citizen app adds advice about scams and the 1930 cyber fraud helpline to this answer.
Verification is limited to 30 requests a minute and 300 a day per IP address, and the backend never logs the hash against the case.
How institutions detect tampering
The projector refuses bad blocks
Before the backend applies any block to its read models, the projector checks:
- that the block's
previous_hashmatches the last block it applied, and that the data hash is correct; - that the channel is
firnational, and that block 0 matches the pinned genesis hash; - that the orderer signatures satisfy the channel's block validation policy, validated the way Fabric validates MSP identities, including revocation lists.
A block that fails changes nothing and raises a critical alert (HASH_CHAIN_BROKEN or ORDERER_SIGNATURE_INVALID) that names the institution whose peer delivered it. The projector then asks the next peer for the same block.
A daily integrity check compares copies
Every night at 03:00 IST, and whenever an auditor asks from the console, the worker's integrity job runs independently of the projector:
- It re-reads every block since its last checkpoint and recomputes the hash chain and the orderer signatures.
- It compares each block with a second institution's peer. A difference raises
PEER_DISAGREES, naming that institution. - It compares the read-model rows written in those blocks with the ledger's world state, value by value. A difference raises
WORLD_STATE_DIVERGESand names the record, the block and the peer that answered. - It records the result as
CONSISTENT,DIVERGENTorINCOMPLETEin the integrity reports, which the console shows.
This check was tested on the development network. After a clean run, two rows were changed directly in PostgreSQL: a revoked key was set back to active, and a committed filing was marked invalid. The next run found both, raised a WORLD_STATE_DIVERGES alert for each and named the peer, block and transaction. After the rows were restored, the run was CONSISTENT again.
Each institution can verify on its own
Any institution can run the backend image in verifier mode against its own peer. That instance runs only the projector and the explorer, so the institution can check the whole ledger and its integrity without trusting the operator of the main backend.
The ledger explorer
The officials' console includes a read-only explorer for auditors, magistrates, court staff, High Court registries and registrars. It shows:
- blocks, with header hashes recomputed in the browser;
- transactions, with the action, signer, endorsements and validation code;
- the full version history of any record;
- integrity runs and their reports;
- ledger alerts.
What each person sees is limited by their role and jurisdiction. Transactions outside their scope appear as withheld.
The off-chain audit log
Some events never reach the ledger, and the backend logs them in an append-only audit.log table instead: signed private reads (which cases and parts, never the values), file downloads, actions the ledger refused (with their signatures), and administrative operations.
- Each row is chained to the writer's previous row by SHA-256. A database trigger refuses any update or delete.
- Every night at 01:00 IST the previous day's rows and their chain heads are exported to write-once (WORM) storage and to the CERT-In log store.
- Logs are kept in India for 180 days, as the CERT-In directions require.
Sources: digifir-backend docs/architecture/BACKEND.md sections 5.4, 6.1, 8, 9.6 and 15.4, ARCHITECTURE.md sections 5.5 and 5.6, docs/NETWORK.md (scenario S7), docs/CONSOLE.md; digifir-chaincode docs/SPEC.md sections 3.7, 3.11 and 4.2.