diff --git a/backend/cache.py b/backend/cache.py index 260d5f88..2aa52e07 100644 --- a/backend/cache.py +++ b/backend/cache.py @@ -180,6 +180,7 @@ def invalidate(self): blockchain_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1) grievance_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1) resolution_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1) +token_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1) visit_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=2) audit_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=2) evidence_audit_last_hash_cache = ThreadSafeCache(ttl=3600, max_size=1) diff --git a/backend/models.py b/backend/models.py index 71c35605..dd93ddcd 100644 --- a/backend/models.py +++ b/backend/models.py @@ -323,6 +323,10 @@ class ResolutionProofToken(Base): valid_from = Column(DateTime, nullable=True) valid_until = Column(DateTime, nullable=True) + # Blockchain integrity fields + integrity_hash = Column(String, nullable=True) + previous_integrity_hash = Column(String, nullable=True, index=True) + # Relationship grievance = relationship("Grievance", back_populates="resolution_tokens") diff --git a/backend/resolution_proof_service.py b/backend/resolution_proof_service.py index ea142571..d5d71ef1 100644 --- a/backend/resolution_proof_service.py +++ b/backend/resolution_proof_service.py @@ -26,7 +26,7 @@ EvidenceAuditLog, VerificationStatus, GrievanceStatus ) from backend.config import get_config, get_auth_config -from backend.cache import resolution_last_hash_cache, evidence_audit_last_hash_cache +from backend.cache import resolution_last_hash_cache, evidence_audit_last_hash_cache, token_last_hash_cache logger = logging.getLogger(__name__) @@ -189,6 +189,22 @@ def generate_proof_token( signature = ResolutionProofService._sign_payload(payload) + # Generate Integrity Hash + prev_hash = token_last_hash_cache.get("last_hash") + if prev_hash is None: + # Cache miss: Fetch only the last hash from DB + last_record = db.query(ResolutionProofToken.integrity_hash).order_by(ResolutionProofToken.id.desc()).first() + prev_hash = last_record[0] if last_record and last_record[0] else "" + token_last_hash_cache.set(data=prev_hash, key="last_hash") + + hash_content = f"{token_uuid}|{grievance_id}|{authority_email}|{now.isoformat()}|{prev_hash}" + secret_key = get_auth_config().secret_key + integrity_hash = hmac.new( + secret_key.encode('utf-8'), + hash_content.encode('utf-8'), + hashlib.sha256 + ).hexdigest() + # Create token record token = ResolutionProofToken( token_id=token_uuid, @@ -203,12 +219,16 @@ def generate_proof_token( nonce=nonce, token_signature=signature, is_used=False, + integrity_hash=integrity_hash, + previous_integrity_hash=prev_hash, ) db.add(token) db.commit() db.refresh(token) + token_last_hash_cache.set(data=integrity_hash, key="last_hash") + logger.info( f"Generated RPT {token_uuid} for grievance {grievance_id} " f"by {authority_email}, expires {valid_until.isoformat()}"