Development of Blockchain Medical Records Storage System
Medical data is the most sensitive category of personal information. Blockchain here solves a real problem: patient medical history is fragmented across different clinics, patients have no control over their own data, and transferring records between institutions is a bureaucratic nightmare. A decentralized system changes ownership: patient controls access to their data through cryptographic keys.
Architectural Principle: Data Off-Chain, Control On-Chain
Storing medical records directly in blockchain is a flawed solution for several reasons. First, HIPAA, GDPR, and most national health data laws require data deletion capability — this is incompatible with blockchain immutability. Second, data size (images, lab results, videos) makes on-chain storage economically infeasible. The correct architecture:
- On-chain: links to data (content-addressed hash), access rights, audit log, consent records
- Off-chain: encrypted medical data in HIPAA-compliant storage (S3, Azure Health Data Services) or decentralized storage (Ceramic, Filecoin with encryption)
Encryption and Key Management
Key idea: data is encrypted with a symmetric key (AES-256). This data encryption key (DEK) is encrypted with the patient's public key. To grant access to a doctor — DEK is re-encrypted with the doctor's public key (proxy re-encryption).
Medical record → encrypt AES-256 → encrypted data (in IPFS/Filecoin)
DEK → encrypt with patient's public key → encrypted DEK (in smart contract)
Doctor's access:
encrypted DEK → proxy re-encryption → encrypted DEK for doctor
Doctor decrypts with their private key → DEK → decrypts data
Proxy re-encryption (libraries: NuCypher/Threshold Network, literature: PRE schemes) allows delegating access without revealing the original key. This is the best approach for medical systems: patient grants doctor access for a specific period and specific records.
Smart Contract Architecture
EHR Registry (Electronic Health Records)
contract EHRRegistry {
struct MedicalRecord {
bytes32 contentHash; // IPFS CID or hash of encrypted data
string storageURI; // URI to retrieve data
bytes encryptedDEK; // DEK encrypted with patient key
uint256 timestamp;
address createdBy; // address of medical institution
RecordType recordType; // DIAGNOSIS, LAB_RESULT, PRESCRIPTION, IMAGING
bool active;
}
enum RecordType { DIAGNOSIS, LAB_RESULT, PRESCRIPTION, IMAGING, VACCINATION, SURGERY }
// patientId => recordId => MedicalRecord
mapping(bytes32 => mapping(bytes32 => MedicalRecord)) private records;
// patientId => recordIds
mapping(bytes32 => bytes32[]) private patientRecords;
// Access rights: patientId => granteeAddress => AccessGrant
mapping(bytes32 => mapping(address => AccessGrant)) private accessGrants;
struct AccessGrant {
bytes encryptedDEK; // DEK re-encrypted with grantee key
uint256 expiresAt;
RecordType[] allowedTypes; // empty array = all types
bool active;
}
// Only authorized medical providers can create records
mapping(address => bool) public authorizedProviders;
event RecordAdded(bytes32 indexed patientId, bytes32 indexed recordId, RecordType recordType);
event AccessGranted(bytes32 indexed patientId, address indexed grantee, uint256 expiresAt);
event AccessRevoked(bytes32 indexed patientId, address indexed grantee);
function addRecord(
bytes32 patientId,
bytes32 recordId,
bytes32 contentHash,
string calldata storageURI,
bytes calldata encryptedDEK,
RecordType recordType
) external onlyAuthorizedProvider {
records[patientId][recordId] = MedicalRecord({
contentHash: contentHash,
storageURI: storageURI,
encryptedDEK: encryptedDEK,
timestamp: block.timestamp,
createdBy: msg.sender,
recordType: recordType,
active: true
});
patientRecords[patientId].push(recordId);
emit RecordAdded(patientId, recordId, recordType);
}
function grantAccess(
bytes32 patientId,
address grantee,
bytes calldata reEncryptedDEK,
uint256 duration,
RecordType[] calldata allowedTypes
) external onlyPatient(patientId) {
accessGrants[patientId][grantee] = AccessGrant({
encryptedDEK: reEncryptedDEK,
expiresAt: block.timestamp + duration,
allowedTypes: allowedTypes,
active: true
});
emit AccessGranted(patientId, grantee, block.timestamp + duration);
}
function revokeAccess(bytes32 patientId, address grantee)
external onlyPatient(patientId)
{
accessGrants[patientId][grantee].active = false;
emit AccessRevoked(patientId, grantee);
}
}
Audit Trail
Every access to records is logged immutably:
contract AuditTrail {
struct AuditEntry {
bytes32 patientId;
bytes32 recordId;
address accessor;
string action; // "READ", "WRITE", "GRANT", "REVOKE"
uint256 timestamp;
bytes32 transactionHash;
}
// Append-only log
AuditEntry[] public auditLog;
mapping(bytes32 => uint256[]) public patientAuditLog; // patientId => indices
function logAccess(
bytes32 patientId,
bytes32 recordId,
string calldata action
) internal {
uint256 index = auditLog.length;
auditLog.push(AuditEntry({
patientId: patientId,
recordId: recordId,
accessor: msg.sender,
action: action,
timestamp: block.timestamp,
transactionHash: bytes32(0) // filled on emit
}));
patientAuditLog[patientId].push(index);
}
}
Regulatory Compliance
GDPR and Right to Deletion
Blockchain is immutable, but off-chain data can be deleted. Pattern: on deletion — destroy data in storage, DEK becomes inaccessible → encrypted blob in IPFS becomes useless. On-chain only hash and metadata remain — this is not personal data by definition (hash cannot recover original data).
function deactivateRecord(bytes32 patientId, bytes32 recordId)
external onlyPatient(patientId)
{
records[patientId][recordId].active = false;
// Off-chain: service deletes encrypted data from storage
// and destroys DEK
emit RecordDeactivated(patientId, recordId);
}
HL7 FHIR Compatibility
For integration with existing medical systems — data is stored in FHIR (Fast Healthcare Interoperability Resources) JSON format. FHIR resources: Patient, Observation, DiagnosticReport, Condition, MedicationRequest.
Storage structure: FHIR JSON → AES-256 encrypt → IPFS → content hash on-chain. On retrieval: decrypt → parse FHIR JSON → convert to required format.
DID (Decentralized Identifiers)
Patients and providers are identified via DID (W3C standard) instead of raw Ethereum addresses. This ensures key rotation (changing keys without losing identity) and cross-system interoperability.
did:ethr:0x742d35... — DID based on Ethereum address
did:web:hospital.example.com — DID based on domain
did:key:z6Mkf... — DID based on public key
Integration with Healthcare Providers
For hospitals and clinics — integration via FHIR API of existing EMR (Electronic Medical Records) systems: Epic, Cerner, Meditech. Adapter service: reads from EMR → converts to standard format → encrypts → publishes on-chain.
| Component | Technology |
|---|---|
| Smart contracts | Solidity + OpenZeppelin |
| Encryption | AES-256-GCM + RSA or ECIES |
| Proxy re-encryption | Threshold Network / NuCypher |
| DID | did:ethr + DID Resolver |
| Storage | IPFS + Filecoin or AWS S3 HIPAA |
| FHIR | HAPI FHIR (Java) or medplum (TypeScript) |
| Indexing | The Graph |
Timeline and Cost
| Phase | Content | Timeline |
|---|---|---|
| Architecture | DID scheme, FHIR mapping, threat model | 1-2 weeks |
| Core contracts | Registry, consent, audit | 3-4 weeks |
| Encryption layer | Key management, proxy re-encryption | 2-3 weeks |
| Storage integration | IPFS/Filecoin, FHIR parser | 2-3 weeks |
| Provider integration | FHIR API adapter | 2-4 weeks |
| Frontend | Patient portal, provider UI | 3-4 weeks |
| Security audit | Contracts + crypto implementation | 2-4 weeks |
Full production-ready system: 4-6 months. MVP without proxy re-encryption and FHIR integration: 2-3 months.







