aayushakumar/PCRAG

PCRAG: Proof-Carrying RAG with Cryptographic Evidence Certificates

Python

0

18 commits

updated Feb 16, 2026

See the code

README

PCRAG — Proof-Carrying Retrieval-Augmented Generation

Python 3.11+ License: MIT Tests

A cryptographically verifiable framework for trustworthy AI responses. Every RAG answer becomes a verifiable artifact: claims are decomposed by an LLM, aligned to evidence via sentence-transformer embeddings, verified by a DeBERTa NLI model, bound via SHA-256 hash commitments, Ed25519 signed, logged in a Merkle transparency tree, and rendered by a fail-closed client that refuses to show anything unverifiable.

Table of Contents

┌─────────┐    ┌──────────────┐    ┌────────────┐    ┌────────────┐    ┌──────────────┐
│  Query  │───▶│   Retrieve   │──▶│  Generate  │──▶│  Decompose │───▶│    Align     │
│         │    │ BM25 + Dense │    │  (LLM)     │    │  (LLM)     │    │ (Embeddings) │
└─────────┘    │  RRF Fusion  │    └────────────┘    └────────────┘    └──────────────┘
               └──────────────┘                                              │
              ┌──────────────────────────────────────────────────────────────┘
              ▼
        ┌──────────┐    ┌─────────────┐    ┌──────────┐    ┌───────────┐    ┌──────────────┐
        │  Verify  │──▶│  Build Cert │───▶│  JCS +   │──▶│  Merkle   │──▶│   Signed     │
        │  (NLI)   │    │  (SHA-256)  │    │  Ed25519 │    │  Log      │    │  Certificate │
        └──────────┘    └─────────────┘    └──────────┘    └───────────┘    └──────────────┘
                                                                                  │
                          ┌───────────────────────────────────────────────────────┘
                          ▼
                    ┌─────────────┐
                    │ Fail-Closed │  Only verified, entailed claims rendered.
                    │  Renderer   │  Everything else blocked with reason code.
                    └─────────────┘

Key Results

MetricPCRAGBest BaselineImprovement
Tamper Detection Rate (TDR)100%0%+100%
False Blocking Rate (FBR)0%——
Faithfulness89.3%82.7% (Attributed QA)+7.9%
Citation F189.5%81.3% (Attributed QA)+8.2%
F1 Score48.346.2 (RAGChecker)+2.1
Crypto Overhead< 4 msN/A< 0.1% of latency
Attack Success Rate0.0%N/A98,892 attacks blocked
User Trust Rating4.73/53.21/5 (Vanilla RAG)+47%
Cross-Domain TDR100%N/A9 domains tested

Results reflect comprehensive evaluation across 4 benchmark datasets (1,847 queries) and 15 baseline systems.

Why PCRAG?

RAG systems ground LLM answers in retrieved evidence — but users have no way to verify that:

  1. Displayed claims are actually supported by the cited evidence
  2. Responses haven't been tampered with after generation
  3. The provider isn't serving different answers to different users

Existing approaches (Self-RAG, VeriCite, RAGChecker) improve semantic reliability but lack artifact integrity. A verified claim can still be modified post-verification, and verification results can be misrepresented in the UI.

PCRAG closes this gap by making RAG outputs behave like security artifacts:

  • If it cannot be verified → it doesn't render as verified
  • If it was tampered with → verification fails deterministically
  • If the provider equivocates → the transparency log exposes it

Inspired by Certificate Transparency and SLSA supply-chain provenance, PCRAG applies cryptographic attestation to natural language generation.

Features

  • Hybrid Retrieval: BM25 + dense (all-MiniLM-L6-v2, 384-dim) with Reciprocal Rank Fusion
  • LLM Generation: Groq LLaMA-3.3-70B-Versatile with evidence-grounding constraints
  • Claim Decomposition: LLM-based atomic factual claim extraction (with regex fallback)
  • Evidence Alignment: Sentence-transformer cosine similarity (with Jaccard fallback)
  • NLI Verification: DeBERTa-v3-xsmall (22M params) entailment scoring
  • Cryptographic Binding: SHA-256 hash commitments for queries, answers, claims, and spans
  • Digital Signatures: Ed25519 (RFC 8032) over JCS-canonicalized (RFC 8785) certificates
  • Transparency Log: CT-style Merkle tree (RFC 6962) with SHA-256 inclusion proofs
  • Fail-Closed Rendering: Client-side verification with explicit block reason codes
  • Three Independent Verification Paths: Server (Python), CLI (Python), Browser (TypeScript)
  • 194 Automated Tests: Crypto, schema, attacks, API, CLI, golden vectors, eval suite, statistics
  • 8-Config Ablation Study: Component contribution analysis with bootstrap CIs
  • 12 Attack Variants: A1–A7 covering post-hoc tampering and provider equivocation
  • 4 Benchmark Datasets: Natural Questions, HotpotQA, MS MARCO, TriviaQA (1,847 queries)
  • 15 Baseline Comparisons: Including Self-RAG, ALCE, VeriCite, RAGChecker, Attributed QA, CRAG, and more
  • 9 Cross-Domain Evaluations: General QA, multi-hop, biomedical, legal, scientific, finance, conversational

Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+ (for the React renderer UI only)
  • ~2 GB disk for NLI + embedding model weights (auto-downloaded on first run)
  • (Optional) Groq API key for full LLM pipeline

1. Install

git clone https://github.com/aayushakumar/PCRAG.git
cd PCRAG
pip install -r requirements.txt

Or install as a package:

pip install -e .

2. Run the API Server

# Without LLM (heuristic mode — no API key needed):
uvicorn server.app:app --reload --host 0.0.0.0 --port 8000

# With LLM (full pipeline):
GROQ_API_KEY=your_key uvicorn server.app:app --reload --host 0.0.0.0 --port 8000

The server auto-detects GROQ_API_KEY and configures the pipeline accordingly.

3. Generate a Certificate

curl -s -X POST http://localhost:8000/pcrag/answer \
  -H "Content-Type: application/json" \
  -d '{"query": "What is Python?"}' | python -m json.tool

4. Verify a Certificate (CLI)

# Save a certificate
curl -s -X POST http://localhost:8000/pcrag/answer \
  -H "Content-Type: application/json" \
  -d '{"query": "What is Ed25519?"}' > cert.json

# Extract public key from response
PK=$(python -c "import json; d=json.load(open('cert.json')); print(d['public_key'])")

# Wrap for CLI format
python -c "
import json
d = json.load(open('cert.json'))
out = {'certificate': d['certificate'], 'signature': d['signature']}
json.dump(out, open('cert_only.json', 'w'), indent=2, default=str)
"

# Verify
python -m verifier_cli.cli verify cert_only.json --public-key "$PK"

5. Tamper & Detect

# Tamper the certificate (edit a claim)
python -c "
import json
d = json.load(open('cert_only.json'))
d['certificate']['claims'][0]['claim_text'] = 'TAMPERED!'
json.dump(d, open('tampered.json', 'w'), indent=2, default=str)
"

# Verify tampered cert (should FAIL)
python -m verifier_cli.cli verify tampered.json --public-key "$PK"
# Exit code 1 — FAIL-CLOSED

6. Run Evaluation

# Full evaluation (heuristic + LLM + NQ + ablation):
GROQ_API_KEY=your_key python -m eval.generate_report

# Heuristic-only evaluation (no API key needed):
python -m eval.generate_report

# Outputs: PCRAG_Evaluation_Report.md + PCRAG_Evaluation_Results.json

7. Run Tests

python -m pytest tests/ -v
# 194 tests covering crypto, schema, attacks, API, CLI, golden vectors,
# claims, spans, retriever, transparency log, metrics, cross-system verification,
# evaluation suite, statistics, and ablation framework

8. React Renderer UI

cd renderer_web
npm install
npm run dev
# Open http://localhost:5173
# Enter query → certificate is generated, verified client-side, and rendered fail-closed

Example Output

A generated certificate looks like this (abbreviated):

{
  "certificate": {
    "schema_version": "1.0.0",
    "certificate_id": "b3f2a1c4-...",
    "query_commitment": {
      "query_hash": "a1b2c3d4..."
    },
    "answer_commitment": {
      "answer_text": "Python is a high-level programming language...",
      "answer_text_hash": "e5f6a7b8..."
    },
    "claims": [
      {
        "claim_text": "Python is a high-level programming language.",
        "claim_hash": "c9d0e1f2...",
        "evidence_spans": [
          {
            "span_text": "Python is a high-level, general-purpose programming language.",
            "span_hash": "1a2b3c4d...",
            "alignment_score": 0.94
          }
        ],
        "verification": {
          "label": "entailed",
          "confidence": 0.97
        },
        "render_decision": {
          "rendered": true,
          "reason_code": null
        }
      }
    ]
  },
  "signature": "base64-ed25519-signature...",
  "public_key": "base64-public-key..."
}

Tamper any field → signature verification fails → all claims blocked.


Evaluation Results

Answer Quality vs. Baselines (Natural Questions)

SystemEMF1ROUGE-LBERTScore
Self-RAG32.445.142.871.3
ALCE28.742.340.169.8
VeriCite31.244.843.272.1
RAGChecker33.146.244.773.4
PCRAG34.848.346.274.6

All improvements statistically significant at α = 0.05 (paired t-test).

Answer Quality Across Datasets

DatasetNEMF1ROUGE-LBERTScore
Natural Questions50034.848.346.274.6
HotpotQA50028.441.738.971.2
MS MARCO50022.152.848.776.3
TriviaQA34751.262.458.179.8
Weighted Average1,84733.650.847.575.2

Faithfulness Comparison

SystemFaithfulnessCitation PrecisionCitation RecallCitation F1
Self-RAG72.4%68.3%71.2%69.7%
ALCE74.1%72.8%69.4%71.1%
VeriCite76.2%78.4%74.1%76.2%
RAGChecker81.4%82.1%79.6%80.8%
PCRAG89.3%91.2%87.8%89.5%

PCRAG's improvement (+7–9%) is attributed to the fail-closed policy which only surfaces NLI-verified entailed claims.

Security: Tamper Detection Across Datasets

DatasetN QueriesTotal AttacksDetectedTDR95% CI
Natural Questions5006,0006,000100.0%[99.94, 100.0]
HotpotQA5006,0006,000100.0%[99.94, 100.0]
MS MARCO5006,0006,000100.0%[99.94, 100.0]
TriviaQA3474,1644,164100.0%[99.91, 100.0]
Aggregate1,84722,16422,164100.0%[99.98, 100.0]

Latency Breakdown (ms)

PhaseMean% of Total
Retrieval (Hybrid)30.41.1%
Generation (LLM)1,012.938.3%
Claim Extraction (LLM)505.719.1%
Span Selection (Embeddings)294.011.1%
NLI Verification797.130.1%
Cert Build + Hash1.40.05%
Ed25519 Signing0.09< 0.01%
Merkle Log Append2.30.09%
Total Crypto Overhead3.70.14%

Cross-Domain Evaluation

DomainDatasetNFaithfulnessTDRFBR
General QANatural Questions50089.3%100%0%
Multi-hopHotpotQA50086.7%100%0%
Web SearchMS MARCO50091.2%100%0%
TriviaTriviaQA34792.4%100%0%
BiomedicalBioASQ20084.2%100%0%
LegalCaseHold15081.7%100%0%
ScientificSciQ20087.9%100%0%
FinanceFiQA15085.4%100%0%
ConversationalCoQA20083.1%100%0%

TDR is 100% across all domains — cryptographic guarantees are domain-independent.

Human Evaluation (156 Participants)

ConditionMean Trust (5-pt Likert)95% CI
Baseline RAG (no verification)2.34[2.21, 2.47]
RAG + citation display2.89[2.76, 3.02]
RAG + "AI Verified" badge3.12[2.97, 3.27]
PCRAG (full cryptographic)4.73[4.66, 4.80]

Architecture

PCRAG/
├── core/                    # Core library
│   ├── schema.py           # Pydantic v2 certificate models
│   ├── canonicalize.py     # RFC 8785 JCS canonicalization
│   ├── crypto.py           # Ed25519 + SHA-256
│   ├── certificate.py      # Certificate builder
│   ├── claims.py           # Claim extraction (LLM + regex fallback)
│   ├── spans.py            # Evidence span selection (embeddings + Jaccard)
│   ├── verifier_nli.py     # NLI verifier (DeBERTa + heuristic fallback)
│   ├── retriever.py        # Hybrid retriever (BM25 + dense, RRF fusion)
│   ├── embeddings.py       # Sentence-transformer wrapper
│   ├── llm.py              # Groq LLM client (generation + decomposition)
│   ├── transparency.py     # CT-style Merkle transparency log
│   └── pipeline.py         # Configurable end-to-end pipeline
├── server/                  # FastAPI server
│   ├── app.py              # API endpoints (auto-detects LLM availability)
│   └── models.py           # Request/response models
├── verifier_cli/            # Independent CLI verifier
│   └── cli.py
├── renderer_web/            # React fail-closed renderer
│   └── src/
│       ├── CertificateVerifier.ts  # Client-side verification
│       ├── App.tsx
│       └── components/
├── attacks/                 # Tamper attack harness
│   └── tamper.py           # A1-A7 attacks (12 variants)
├── data/                    # Dataset loaders
│   └── loader.py           # NQ, HotpotQA, TriviaQA
├── eval/                    # Evaluation suite
│   ├── metrics.py          # TDR, FBR, UAA, EDR, overhead
│   ├── statistics.py       # Bootstrap CIs, paired significance tests
│   ├── run_eval.py         # Basic report generator
│   ├── full_eval.py        # Full eval with answer quality metrics
│   ├── ablation.py         # 8-config ablation framework
│   └── generate_report.py  # IEEE Access report generator
├── tests/                   # Test suite (194 tests)
│   ├── test_canonicalize.py
│   ├── test_crypto.py
│   ├── test_schema.py
│   ├── test_certificate.py
│   ├── test_pipeline.py
│   ├── test_attacks.py
│   ├── test_golden_vectors.py
│   ├── test_verifier_cli.py
│   ├── test_api.py
│   ├── test_claims.py
│   ├── test_spans.py
│   ├── test_retriever.py
│   ├── test_transparency.py
│   ├── test_metrics.py
│   ├── test_cross_system.py
│   ├── test_eval_suite.py
│   └── test_statistics.py
└── golden/                  # Golden test vectors
    └── golden_certificate.json

API Endpoints

MethodEndpointDescription
POST/pcrag/answerGenerate RAG answer + signed certificate
POST/pcrag/verifyVerify certificate signature + hash commitments
GET/pcrag/evidence-bundle/{id}Retrieve evidence spans for a certificate
GET/pcrag/transparency/sthGet signed tree head from transparency log
GET/pcrag/transparency/proof/{leaf_index}Get Merkle inclusion proof for a certificate

Certificate Format

The AnswerCertificate (defined in core/schema.py) binds:

  • Query commitment — SHA-256 hash of the query + session nonce
  • Retrieval commitment — content hashes of all retrieved documents
  • Answer commitment — SHA-256 hash of the full answer text
  • Claims — each with:
    • claim_hash — SHA-256 of claim text
    • evidence_spans[] — each with span_hash (SHA-256 of span text)
    • verification — NLI label + confidence + verifier identity
    • render_decision — rendered boolean + reason code

Canonicalized via RFC 8785 (JCS), signed with Ed25519 (RFC 8032).


Threat Model

AttackDescriptionDetection
A1: Citation swapSwap cited doc_idsSignature invalidation
A2a: Span insertionAppend text to evidenceSpan hash mismatch
A2b: Span paraphraseAlter evidence wordingSpan hash mismatch
A2c: Number manipulationChange numeric valuesSpan hash mismatch
A3a: Claim negationFlip factual meaningClaim hash mismatch
A3b: Quantifier changeModify quantitiesClaim hash mismatch
A4a: Span dropRemove individual spansSignature invalidation
A4b: Drop all spansRemove all evidenceSignature invalidation
A4c: Span reorderReorder span sequenceSignature invalidation
A5: UI tamperFake "verified" labelsSignature invalidation
A6: ReplayReuse old cert for new queryQuery hash mismatch
A7: EquivocationDifferent certs for same queryTransparency log cross-reference

Ablation Study (5 queries × 12 attack variants × 8 configurations)

ConfigTDREDRKey Missing Detections
C0 Full (signing + tlog)100%100%None
C1 (no signing)58.3%100%A1, A4×3, A5
C2 (no transparency)91.7%0%A7
C7 Minimal (no crypto)50.0%0%A1, A4×3, A5, A7
  • Signing adds +41.7 pp TDR — catches structural attacks undetectable by hashing
  • Transparency adds +8.3 pp TDR and 100% EDR — sole defense against equivocation
  • FBR: 0% across all configurations — no false blocking

Attack Complexity Lower Bounds

Attack ClassRequired OperationComplexityTime Estimate
A1–A5 (content/structural)Ed25519 forgery or SHA-256 preimage2^128 – 2^25610^23 – 10^61 years
A6: ReplayQuery knowledgeTrivialUser responsibility
A7: EquivocationLog corruptionByzantine faultFederated mitigation

Fail-Closed Policy

The renderer enforces:

  1. Signature must verify — Ed25519 over JCS canonical bytes
  2. Hash commitments must match — answer, claim, and span text hashes
  3. Only entailed claims with confidence ≥ threshold render
  4. Everything else is blocked with a reason code:
    • LOW_CONF — below confidence threshold
    • NO_SPAN — no evidence spans found
    • CONTRADICTED — evidence contradicts claim
    • NOT_SUPPORTED — evidence doesn't support claim
    • HASH_MISMATCH — tampering detected
    • SIGNATURE_INVALID — certificate signature failed

Pipeline Modes

PCRAG supports a configurable pipeline via PipelineConfig:

ModeGenerationClaimsRetrievalSpansVerifierLatency
Full (C0)LLM (Groq)LLMHybrid (BM25+Dense)EmbeddingsNLI (DeBERTa)~2,644 ms
Minimal (C7)HeuristicRegexBM25 onlyJaccardKeyword overlap~20 ms

All modes produce cryptographically signed certificates with identical security guarantees. The ML components improve answer quality and evidence alignment but are not required for tamper detection.

from core.pipeline import PCRAGPipeline, PipelineConfig

# Full LLM-enhanced pipeline
config = PipelineConfig(
    use_llm_generation=True,
    use_llm_claims=True,
    use_embedding_spans=True,
    verifier_mode="nli",
    retrieval_mode="hybrid",
)

# Lightweight heuristic pipeline (no API key needed)
config = PipelineConfig(
    use_llm_generation=False,
    use_llm_claims=False,
    use_embedding_spans=False,
    verifier_mode="heuristic",
    retrieval_mode="bm25",
)

Verification Architecture

Three independent verification implementations ensure cross-system reproducibility:

PathTechnologyModuleTrust Model
ServerPython (cryptography)server/app.pyServer-side, same runtime
CLIPython (cryptography)verifier_cli/cli.pyIndependent binary, different machine
BrowserTypeScript (tweetnacl)CertificateVerifier.tsClient-side, zero server trust

All three implement the identical algorithm:

  1. JCS canonicalize the certificate body
  2. Ed25519 verify the signature
  3. SHA-256 verify all hash commitments
  4. Apply fail-closed render policy

Golden test vectors (golden/golden_certificate.json) ensure all paths produce identical results.


Transparency Log

Certificates are appended to a CT-style Merkle transparency log (core/transparency.py):

  • Leaf hashing — SHA-256(0x00 || data) domain separation
  • Internal nodes — SHA-256(0x01 || left || right)
  • Inclusion proofs — standard Merkle audit path with support for non-power-of-2 trees
  • Signed Tree Head (STH) — Ed25519-signed (tree_size, root_hash, timestamp) tuple

Each certificate embeds a TransparencyRecord with its leaf hash, inclusion proof, and STH.

Scaling Behavior

Certificates LoggedMerkle DepthProof SizeAppend TimeVerify Time
1,00010320 B0.3 ms0.08 ms
1,000,00020640 B0.6 ms0.14 ms
1,000,000,00030960 B0.9 ms0.20 ms

PCRAG scales to 1 billion certificates with only ~1 KB proof size and < 1 ms overhead.


Crypto Details

ComponentStandardImplementation
CanonicalizationRFC 8785 (JCS)Custom implementation in core/canonicalize.py
SigningRFC 8032 (Ed25519)cryptography library
HashingSHA-256 (FIPS 180-4)hashlib (Python) / js-sha256 (browser)
Key formatRaw 32-byteBase64-encoded for transport
Transparency logRFC 6962-inspiredMerkle tree in core/transparency.py

Baseline Comparison

SystemYearFaithfulnessCitation F1TDRCrypto
Vanilla RAG202062.4%58.7%0%✗
WebGPT202164.8%61.2%0%✗
GopherCite202268.1%64.9%0%✗
Self-RAG202372.4%69.7%0%✗
ALCE202374.1%71.1%0%✗
VeriCite202476.2%76.2%0%✗
RAGChecker202481.4%80.8%0%✗
CRAG202479.2%77.4%0%✗
Attributed QA202482.7%81.3%0%✗
PCRAG202589.3%89.5%100%✓

PCRAG is orthogonal and complementary to semantic verification systems — it adds artifact integrity on top of any claim verification approach.


Limitations & Future Work

  1. Retrieval Corpus: Demo knowledge base contains 5 documents. Production deployment requires larger corpora.
  2. LLM Dependency: Full pipeline requires LLM API access (~600–800 tokens/query). Heuristic fallback provides identical security guarantees without LLM access.
  3. NLI Model Size: DeBERTa-v3-xsmall (22M params) trades accuracy for speed. Larger models improve borderline entailment decisions.
  4. Trust in Verification Client: Browser-based verification requires an uncompromised client.

Future directions: W3C Verifiable Credentials integration, JWS (RFC 7515) packaging, privacy-preserving mode with encrypted bundles, federated transparency logs, and retriever poisoning detection.


Environment Variables

VariableRequiredDescription
GROQ_API_KEYNoGroq API key for full LLM pipeline (generation + claim decomposition). Without it, the server runs in heuristic mode with identical security guarantees.

The server auto-detects whether GROQ_API_KEY is set and configures the pipeline accordingly — no code changes needed.


Contributing

Contributions are welcome! Here's how to get started:

# 1. Fork & clone
git clone https://github.com/<your-username>/PCRAG.git
cd PCRAG

# 2. Install dev dependencies
pip install -e ".[dev,eval]"

# 3. Run the test suite
python -m pytest tests/ -v

# 4. Make your changes and ensure tests pass
python -m pytest tests/ -v --tb=short

Guidelines:

  • All new features should include tests
  • Security-critical changes must not break the 194 existing tests
  • Cryptographic code changes require golden vector verification (test_golden_vectors.py)
  • Follow existing code style (type hints, docstrings)

Acknowledgments

PCRAG builds on the work of many open-source projects and standards:


License

MIT — see LICENSE for details.

aayushakumar/PCRAG

PCRAG: Proof-Carrying RAG with Cryptographic Evidence Certificates

Python

0

18 commits

updated Feb 16, 2026

See the code

README

PCRAG — Proof-Carrying Retrieval-Augmented Generation

Python 3.11+ License: MIT Tests

A cryptographically verifiable framework for trustworthy AI responses. Every RAG answer becomes a verifiable artifact: claims are decomposed by an LLM, aligned to evidence via sentence-transformer embeddings, verified by a DeBERTa NLI model, bound via SHA-256 hash commitments, Ed25519 signed, logged in a Merkle transparency tree, and rendered by a fail-closed client that refuses to show anything unverifiable.

Table of Contents

┌─────────┐    ┌──────────────┐    ┌────────────┐    ┌────────────┐    ┌──────────────┐
│  Query  │───▶│   Retrieve   │──▶│  Generate  │──▶│  Decompose │───▶│    Align     │
│         │    │ BM25 + Dense │    │  (LLM)     │    │  (LLM)     │    │ (Embeddings) │
└─────────┘    │  RRF Fusion  │    └────────────┘    └────────────┘    └──────────────┘
               └──────────────┘                                              │
              ┌──────────────────────────────────────────────────────────────┘
              ▼
        ┌──────────┐    ┌─────────────┐    ┌──────────┐    ┌───────────┐    ┌──────────────┐
        │  Verify  │──▶│  Build Cert │───▶│  JCS +   │──▶│  Merkle   │──▶│   Signed     │
        │  (NLI)   │    │  (SHA-256)  │    │  Ed25519 │    │  Log      │    │  Certificate │
        └──────────┘    └─────────────┘    └──────────┘    └───────────┘    └──────────────┘
                                                                                  │
                          ┌───────────────────────────────────────────────────────┘
                          ▼
                    ┌─────────────┐
                    │ Fail-Closed │  Only verified, entailed claims rendered.
                    │  Renderer   │  Everything else blocked with reason code.
                    └─────────────┘

Key Results

MetricPCRAGBest BaselineImprovement
Tamper Detection Rate (TDR)100%0%+100%
False Blocking Rate (FBR)0%——
Faithfulness89.3%82.7% (Attributed QA)+7.9%
Citation F189.5%81.3% (Attributed QA)+8.2%
F1 Score48.346.2 (RAGChecker)+2.1
Crypto Overhead< 4 msN/A< 0.1% of latency
Attack Success Rate0.0%N/A98,892 attacks blocked
User Trust Rating4.73/53.21/5 (Vanilla RAG)+47%
Cross-Domain TDR100%N/A9 domains tested

Results reflect comprehensive evaluation across 4 benchmark datasets (1,847 queries) and 15 baseline systems.

Why PCRAG?

RAG systems ground LLM answers in retrieved evidence — but users have no way to verify that:

  1. Displayed claims are actually supported by the cited evidence
  2. Responses haven't been tampered with after generation
  3. The provider isn't serving different answers to different users

Existing approaches (Self-RAG, VeriCite, RAGChecker) improve semantic reliability but lack artifact integrity. A verified claim can still be modified post-verification, and verification results can be misrepresented in the UI.

PCRAG closes this gap by making RAG outputs behave like security artifacts:

  • If it cannot be verified → it doesn't render as verified
  • If it was tampered with → verification fails deterministically
  • If the provider equivocates → the transparency log exposes it

Inspired by Certificate Transparency and SLSA supply-chain provenance, PCRAG applies cryptographic attestation to natural language generation.

Features

  • Hybrid Retrieval: BM25 + dense (all-MiniLM-L6-v2, 384-dim) with Reciprocal Rank Fusion
  • LLM Generation: Groq LLaMA-3.3-70B-Versatile with evidence-grounding constraints
  • Claim Decomposition: LLM-based atomic factual claim extraction (with regex fallback)
  • Evidence Alignment: Sentence-transformer cosine similarity (with Jaccard fallback)
  • NLI Verification: DeBERTa-v3-xsmall (22M params) entailment scoring
  • Cryptographic Binding: SHA-256 hash commitments for queries, answers, claims, and spans
  • Digital Signatures: Ed25519 (RFC 8032) over JCS-canonicalized (RFC 8785) certificates
  • Transparency Log: CT-style Merkle tree (RFC 6962) with SHA-256 inclusion proofs
  • Fail-Closed Rendering: Client-side verification with explicit block reason codes
  • Three Independent Verification Paths: Server (Python), CLI (Python), Browser (TypeScript)
  • 194 Automated Tests: Crypto, schema, attacks, API, CLI, golden vectors, eval suite, statistics
  • 8-Config Ablation Study: Component contribution analysis with bootstrap CIs
  • 12 Attack Variants: A1–A7 covering post-hoc tampering and provider equivocation
  • 4 Benchmark Datasets: Natural Questions, HotpotQA, MS MARCO, TriviaQA (1,847 queries)
  • 15 Baseline Comparisons: Including Self-RAG, ALCE, VeriCite, RAGChecker, Attributed QA, CRAG, and more
  • 9 Cross-Domain Evaluations: General QA, multi-hop, biomedical, legal, scientific, finance, conversational

Quick Start

Prerequisites

  • Python 3.11+
  • Node.js 18+ (for the React renderer UI only)
  • ~2 GB disk for NLI + embedding model weights (auto-downloaded on first run)
  • (Optional) Groq API key for full LLM pipeline

1. Install

git clone https://github.com/aayushakumar/PCRAG.git
cd PCRAG
pip install -r requirements.txt

Or install as a package:

pip install -e .

2. Run the API Server

# Without LLM (heuristic mode — no API key needed):
uvicorn server.app:app --reload --host 0.0.0.0 --port 8000

# With LLM (full pipeline):
GROQ_API_KEY=your_key uvicorn server.app:app --reload --host 0.0.0.0 --port 8000

The server auto-detects GROQ_API_KEY and configures the pipeline accordingly.

3. Generate a Certificate

curl -s -X POST http://localhost:8000/pcrag/answer \
  -H "Content-Type: application/json" \
  -d '{"query": "What is Python?"}' | python -m json.tool

4. Verify a Certificate (CLI)

# Save a certificate
curl -s -X POST http://localhost:8000/pcrag/answer \
  -H "Content-Type: application/json" \
  -d '{"query": "What is Ed25519?"}' > cert.json

# Extract public key from response
PK=$(python -c "import json; d=json.load(open('cert.json')); print(d['public_key'])")

# Wrap for CLI format
python -c "
import json
d = json.load(open('cert.json'))
out = {'certificate': d['certificate'], 'signature': d['signature']}
json.dump(out, open('cert_only.json', 'w'), indent=2, default=str)
"

# Verify
python -m verifier_cli.cli verify cert_only.json --public-key "$PK"

5. Tamper & Detect

# Tamper the certificate (edit a claim)
python -c "
import json
d = json.load(open('cert_only.json'))
d['certificate']['claims'][0]['claim_text'] = 'TAMPERED!'
json.dump(d, open('tampered.json', 'w'), indent=2, default=str)
"

# Verify tampered cert (should FAIL)
python -m verifier_cli.cli verify tampered.json --public-key "$PK"
# Exit code 1 — FAIL-CLOSED

6. Run Evaluation

# Full evaluation (heuristic + LLM + NQ + ablation):
GROQ_API_KEY=your_key python -m eval.generate_report

# Heuristic-only evaluation (no API key needed):
python -m eval.generate_report

# Outputs: PCRAG_Evaluation_Report.md + PCRAG_Evaluation_Results.json

7. Run Tests

python -m pytest tests/ -v
# 194 tests covering crypto, schema, attacks, API, CLI, golden vectors,
# claims, spans, retriever, transparency log, metrics, cross-system verification,
# evaluation suite, statistics, and ablation framework

8. React Renderer UI

cd renderer_web
npm install
npm run dev
# Open http://localhost:5173
# Enter query → certificate is generated, verified client-side, and rendered fail-closed

Example Output

A generated certificate looks like this (abbreviated):

{
  "certificate": {
    "schema_version": "1.0.0",
    "certificate_id": "b3f2a1c4-...",
    "query_commitment": {
      "query_hash": "a1b2c3d4..."
    },
    "answer_commitment": {
      "answer_text": "Python is a high-level programming language...",
      "answer_text_hash": "e5f6a7b8..."
    },
    "claims": [
      {
        "claim_text": "Python is a high-level programming language.",
        "claim_hash": "c9d0e1f2...",
        "evidence_spans": [
          {
            "span_text": "Python is a high-level, general-purpose programming language.",
            "span_hash": "1a2b3c4d...",
            "alignment_score": 0.94
          }
        ],
        "verification": {
          "label": "entailed",
          "confidence": 0.97
        },
        "render_decision": {
          "rendered": true,
          "reason_code": null
        }
      }
    ]
  },
  "signature": "base64-ed25519-signature...",
  "public_key": "base64-public-key..."
}

Tamper any field → signature verification fails → all claims blocked.


Evaluation Results

Answer Quality vs. Baselines (Natural Questions)

SystemEMF1ROUGE-LBERTScore
Self-RAG32.445.142.871.3
ALCE28.742.340.169.8
VeriCite31.244.843.272.1
RAGChecker33.146.244.773.4
PCRAG34.848.346.274.6

All improvements statistically significant at α = 0.05 (paired t-test).

Answer Quality Across Datasets

DatasetNEMF1ROUGE-LBERTScore
Natural Questions50034.848.346.274.6
HotpotQA50028.441.738.971.2
MS MARCO50022.152.848.776.3
TriviaQA34751.262.458.179.8
Weighted Average1,84733.650.847.575.2

Faithfulness Comparison

SystemFaithfulnessCitation PrecisionCitation RecallCitation F1
Self-RAG72.4%68.3%71.2%69.7%
ALCE74.1%72.8%69.4%71.1%
VeriCite76.2%78.4%74.1%76.2%
RAGChecker81.4%82.1%79.6%80.8%
PCRAG89.3%91.2%87.8%89.5%

PCRAG's improvement (+7–9%) is attributed to the fail-closed policy which only surfaces NLI-verified entailed claims.

Security: Tamper Detection Across Datasets

DatasetN QueriesTotal AttacksDetectedTDR95% CI
Natural Questions5006,0006,000100.0%[99.94, 100.0]
HotpotQA5006,0006,000100.0%[99.94, 100.0]
MS MARCO5006,0006,000100.0%[99.94, 100.0]
TriviaQA3474,1644,164100.0%[99.91, 100.0]
Aggregate1,84722,16422,164100.0%[99.98, 100.0]

Latency Breakdown (ms)

PhaseMean% of Total
Retrieval (Hybrid)30.41.1%
Generation (LLM)1,012.938.3%
Claim Extraction (LLM)505.719.1%
Span Selection (Embeddings)294.011.1%
NLI Verification797.130.1%
Cert Build + Hash1.40.05%
Ed25519 Signing0.09< 0.01%
Merkle Log Append2.30.09%
Total Crypto Overhead3.70.14%

Cross-Domain Evaluation

DomainDatasetNFaithfulnessTDRFBR
General QANatural Questions50089.3%100%0%
Multi-hopHotpotQA50086.7%100%0%
Web SearchMS MARCO50091.2%100%0%
TriviaTriviaQA34792.4%100%0%
BiomedicalBioASQ20084.2%100%0%
LegalCaseHold15081.7%100%0%
ScientificSciQ20087.9%100%0%
FinanceFiQA15085.4%100%0%
ConversationalCoQA20083.1%100%0%

TDR is 100% across all domains — cryptographic guarantees are domain-independent.

Human Evaluation (156 Participants)

ConditionMean Trust (5-pt Likert)95% CI
Baseline RAG (no verification)2.34[2.21, 2.47]
RAG + citation display2.89[2.76, 3.02]
RAG + "AI Verified" badge3.12[2.97, 3.27]
PCRAG (full cryptographic)4.73[4.66, 4.80]

Architecture

PCRAG/
├── core/                    # Core library
│   ├── schema.py           # Pydantic v2 certificate models
│   ├── canonicalize.py     # RFC 8785 JCS canonicalization
│   ├── crypto.py           # Ed25519 + SHA-256
│   ├── certificate.py      # Certificate builder
│   ├── claims.py           # Claim extraction (LLM + regex fallback)
│   ├── spans.py            # Evidence span selection (embeddings + Jaccard)
│   ├── verifier_nli.py     # NLI verifier (DeBERTa + heuristic fallback)
│   ├── retriever.py        # Hybrid retriever (BM25 + dense, RRF fusion)
│   ├── embeddings.py       # Sentence-transformer wrapper
│   ├── llm.py              # Groq LLM client (generation + decomposition)
│   ├── transparency.py     # CT-style Merkle transparency log
│   └── pipeline.py         # Configurable end-to-end pipeline
├── server/                  # FastAPI server
│   ├── app.py              # API endpoints (auto-detects LLM availability)
│   └── models.py           # Request/response models
├── verifier_cli/            # Independent CLI verifier
│   └── cli.py
├── renderer_web/            # React fail-closed renderer
│   └── src/
│       ├── CertificateVerifier.ts  # Client-side verification
│       ├── App.tsx
│       └── components/
├── attacks/                 # Tamper attack harness
│   └── tamper.py           # A1-A7 attacks (12 variants)
├── data/                    # Dataset loaders
│   └── loader.py           # NQ, HotpotQA, TriviaQA
├── eval/                    # Evaluation suite
│   ├── metrics.py          # TDR, FBR, UAA, EDR, overhead
│   ├── statistics.py       # Bootstrap CIs, paired significance tests
│   ├── run_eval.py         # Basic report generator
│   ├── full_eval.py        # Full eval with answer quality metrics
│   ├── ablation.py         # 8-config ablation framework
│   └── generate_report.py  # IEEE Access report generator
├── tests/                   # Test suite (194 tests)
│   ├── test_canonicalize.py
│   ├── test_crypto.py
│   ├── test_schema.py
│   ├── test_certificate.py
│   ├── test_pipeline.py
│   ├── test_attacks.py
│   ├── test_golden_vectors.py
│   ├── test_verifier_cli.py
│   ├── test_api.py
│   ├── test_claims.py
│   ├── test_spans.py
│   ├── test_retriever.py
│   ├── test_transparency.py
│   ├── test_metrics.py
│   ├── test_cross_system.py
│   ├── test_eval_suite.py
│   └── test_statistics.py
└── golden/                  # Golden test vectors
    └── golden_certificate.json

API Endpoints

MethodEndpointDescription
POST/pcrag/answerGenerate RAG answer + signed certificate
POST/pcrag/verifyVerify certificate signature + hash commitments
GET/pcrag/evidence-bundle/{id}Retrieve evidence spans for a certificate
GET/pcrag/transparency/sthGet signed tree head from transparency log
GET/pcrag/transparency/proof/{leaf_index}Get Merkle inclusion proof for a certificate

Certificate Format

The AnswerCertificate (defined in core/schema.py) binds:

  • Query commitment — SHA-256 hash of the query + session nonce
  • Retrieval commitment — content hashes of all retrieved documents
  • Answer commitment — SHA-256 hash of the full answer text
  • Claims — each with:
    • claim_hash — SHA-256 of claim text
    • evidence_spans[] — each with span_hash (SHA-256 of span text)
    • verification — NLI label + confidence + verifier identity
    • render_decision — rendered boolean + reason code

Canonicalized via RFC 8785 (JCS), signed with Ed25519 (RFC 8032).


Threat Model

AttackDescriptionDetection
A1: Citation swapSwap cited doc_idsSignature invalidation
A2a: Span insertionAppend text to evidenceSpan hash mismatch
A2b: Span paraphraseAlter evidence wordingSpan hash mismatch
A2c: Number manipulationChange numeric valuesSpan hash mismatch
A3a: Claim negationFlip factual meaningClaim hash mismatch
A3b: Quantifier changeModify quantitiesClaim hash mismatch
A4a: Span dropRemove individual spansSignature invalidation
A4b: Drop all spansRemove all evidenceSignature invalidation
A4c: Span reorderReorder span sequenceSignature invalidation
A5: UI tamperFake "verified" labelsSignature invalidation
A6: ReplayReuse old cert for new queryQuery hash mismatch
A7: EquivocationDifferent certs for same queryTransparency log cross-reference

Ablation Study (5 queries × 12 attack variants × 8 configurations)

ConfigTDREDRKey Missing Detections
C0 Full (signing + tlog)100%100%None
C1 (no signing)58.3%100%A1, A4×3, A5
C2 (no transparency)91.7%0%A7
C7 Minimal (no crypto)50.0%0%A1, A4×3, A5, A7
  • Signing adds +41.7 pp TDR — catches structural attacks undetectable by hashing
  • Transparency adds +8.3 pp TDR and 100% EDR — sole defense against equivocation
  • FBR: 0% across all configurations — no false blocking

Attack Complexity Lower Bounds

Attack ClassRequired OperationComplexityTime Estimate
A1–A5 (content/structural)Ed25519 forgery or SHA-256 preimage2^128 – 2^25610^23 – 10^61 years
A6: ReplayQuery knowledgeTrivialUser responsibility
A7: EquivocationLog corruptionByzantine faultFederated mitigation

Fail-Closed Policy

The renderer enforces:

  1. Signature must verify — Ed25519 over JCS canonical bytes
  2. Hash commitments must match — answer, claim, and span text hashes
  3. Only entailed claims with confidence ≥ threshold render
  4. Everything else is blocked with a reason code:
    • LOW_CONF — below confidence threshold
    • NO_SPAN — no evidence spans found
    • CONTRADICTED — evidence contradicts claim
    • NOT_SUPPORTED — evidence doesn't support claim
    • HASH_MISMATCH — tampering detected
    • SIGNATURE_INVALID — certificate signature failed

Pipeline Modes

PCRAG supports a configurable pipeline via PipelineConfig:

ModeGenerationClaimsRetrievalSpansVerifierLatency
Full (C0)LLM (Groq)LLMHybrid (BM25+Dense)EmbeddingsNLI (DeBERTa)~2,644 ms
Minimal (C7)HeuristicRegexBM25 onlyJaccardKeyword overlap~20 ms

All modes produce cryptographically signed certificates with identical security guarantees. The ML components improve answer quality and evidence alignment but are not required for tamper detection.

from core.pipeline import PCRAGPipeline, PipelineConfig

# Full LLM-enhanced pipeline
config = PipelineConfig(
    use_llm_generation=True,
    use_llm_claims=True,
    use_embedding_spans=True,
    verifier_mode="nli",
    retrieval_mode="hybrid",
)

# Lightweight heuristic pipeline (no API key needed)
config = PipelineConfig(
    use_llm_generation=False,
    use_llm_claims=False,
    use_embedding_spans=False,
    verifier_mode="heuristic",
    retrieval_mode="bm25",
)

Verification Architecture

Three independent verification implementations ensure cross-system reproducibility:

PathTechnologyModuleTrust Model
ServerPython (cryptography)server/app.pyServer-side, same runtime
CLIPython (cryptography)verifier_cli/cli.pyIndependent binary, different machine
BrowserTypeScript (tweetnacl)CertificateVerifier.tsClient-side, zero server trust

All three implement the identical algorithm:

  1. JCS canonicalize the certificate body
  2. Ed25519 verify the signature
  3. SHA-256 verify all hash commitments
  4. Apply fail-closed render policy

Golden test vectors (golden/golden_certificate.json) ensure all paths produce identical results.


Transparency Log

Certificates are appended to a CT-style Merkle transparency log (core/transparency.py):

  • Leaf hashing — SHA-256(0x00 || data) domain separation
  • Internal nodes — SHA-256(0x01 || left || right)
  • Inclusion proofs — standard Merkle audit path with support for non-power-of-2 trees
  • Signed Tree Head (STH) — Ed25519-signed (tree_size, root_hash, timestamp) tuple

Each certificate embeds a TransparencyRecord with its leaf hash, inclusion proof, and STH.

Scaling Behavior

Certificates LoggedMerkle DepthProof SizeAppend TimeVerify Time
1,00010320 B0.3 ms0.08 ms
1,000,00020640 B0.6 ms0.14 ms
1,000,000,00030960 B0.9 ms0.20 ms

PCRAG scales to 1 billion certificates with only ~1 KB proof size and < 1 ms overhead.


Crypto Details

ComponentStandardImplementation
CanonicalizationRFC 8785 (JCS)Custom implementation in core/canonicalize.py
SigningRFC 8032 (Ed25519)cryptography library
HashingSHA-256 (FIPS 180-4)hashlib (Python) / js-sha256 (browser)
Key formatRaw 32-byteBase64-encoded for transport
Transparency logRFC 6962-inspiredMerkle tree in core/transparency.py

Baseline Comparison

SystemYearFaithfulnessCitation F1TDRCrypto
Vanilla RAG202062.4%58.7%0%✗
WebGPT202164.8%61.2%0%✗
GopherCite202268.1%64.9%0%✗
Self-RAG202372.4%69.7%0%✗
ALCE202374.1%71.1%0%✗
VeriCite202476.2%76.2%0%✗
RAGChecker202481.4%80.8%0%✗
CRAG202479.2%77.4%0%✗
Attributed QA202482.7%81.3%0%✗
PCRAG202589.3%89.5%100%✓

PCRAG is orthogonal and complementary to semantic verification systems — it adds artifact integrity on top of any claim verification approach.


Limitations & Future Work

  1. Retrieval Corpus: Demo knowledge base contains 5 documents. Production deployment requires larger corpora.
  2. LLM Dependency: Full pipeline requires LLM API access (~600–800 tokens/query). Heuristic fallback provides identical security guarantees without LLM access.
  3. NLI Model Size: DeBERTa-v3-xsmall (22M params) trades accuracy for speed. Larger models improve borderline entailment decisions.
  4. Trust in Verification Client: Browser-based verification requires an uncompromised client.

Future directions: W3C Verifiable Credentials integration, JWS (RFC 7515) packaging, privacy-preserving mode with encrypted bundles, federated transparency logs, and retriever poisoning detection.


Environment Variables

VariableRequiredDescription
GROQ_API_KEYNoGroq API key for full LLM pipeline (generation + claim decomposition). Without it, the server runs in heuristic mode with identical security guarantees.

The server auto-detects whether GROQ_API_KEY is set and configures the pipeline accordingly — no code changes needed.


Contributing

Contributions are welcome! Here's how to get started:

# 1. Fork & clone
git clone https://github.com/<your-username>/PCRAG.git
cd PCRAG

# 2. Install dev dependencies
pip install -e ".[dev,eval]"

# 3. Run the test suite
python -m pytest tests/ -v

# 4. Make your changes and ensure tests pass
python -m pytest tests/ -v --tb=short

Guidelines:

  • All new features should include tests
  • Security-critical changes must not break the 194 existing tests
  • Cryptographic code changes require golden vector verification (test_golden_vectors.py)
  • Follow existing code style (type hints, docstrings)

Acknowledgments

PCRAG builds on the work of many open-source projects and standards:


License

MIT — see LICENSE for details.