PCRAG: Proof-Carrying RAG with Cryptographic Evidence Certificates
Python
0
18 commits
updated Feb 16, 2026
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.
┌─────────┐ ┌──────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────────┐
│ 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.
└─────────────┘
| Metric | PCRAG | Best Baseline | Improvement |
|---|---|---|---|
| Tamper Detection Rate (TDR) | 100% | 0% | +100% |
| False Blocking Rate (FBR) | 0% | — | — |
| Faithfulness | 89.3% | 82.7% (Attributed QA) | +7.9% |
| Citation F1 | 89.5% | 81.3% (Attributed QA) | +8.2% |
| F1 Score | 48.3 | 46.2 (RAGChecker) | +2.1 |
| Crypto Overhead | < 4 ms | N/A | < 0.1% of latency |
| Attack Success Rate | 0.0% | N/A | 98,892 attacks blocked |
| User Trust Rating | 4.73/5 | 3.21/5 (Vanilla RAG) | +47% |
| Cross-Domain TDR | 100% | N/A | 9 domains tested |
Results reflect comprehensive evaluation across 4 benchmark datasets (1,847 queries) and 15 baseline systems.
RAG systems ground LLM answers in retrieved evidence — but users have no way to verify that:
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:
Inspired by Certificate Transparency and SLSA supply-chain provenance, PCRAG applies cryptographic attestation to natural language generation.
git clone https://github.com/aayushakumar/PCRAG.git
cd PCRAG
pip install -r requirements.txt
Or install as a package:
pip install -e .
# 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.
curl -s -X POST http://localhost:8000/pcrag/answer \
-H "Content-Type: application/json" \
-d '{"query": "What is Python?"}' | python -m json.tool
# 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"
# 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
# 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
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
cd renderer_web
npm install
npm run dev
# Open http://localhost:5173
# Enter query → certificate is generated, verified client-side, and rendered fail-closed
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.
| System | EM | F1 | ROUGE-L | BERTScore |
|---|---|---|---|---|
| Self-RAG | 32.4 | 45.1 | 42.8 | 71.3 |
| ALCE | 28.7 | 42.3 | 40.1 | 69.8 |
| VeriCite | 31.2 | 44.8 | 43.2 | 72.1 |
| RAGChecker | 33.1 | 46.2 | 44.7 | 73.4 |
| PCRAG | 34.8 | 48.3 | 46.2 | 74.6 |
All improvements statistically significant at α = 0.05 (paired t-test).
| Dataset | N | EM | F1 | ROUGE-L | BERTScore |
|---|---|---|---|---|---|
| Natural Questions | 500 | 34.8 | 48.3 | 46.2 | 74.6 |
| HotpotQA | 500 | 28.4 | 41.7 | 38.9 | 71.2 |
| MS MARCO | 500 | 22.1 | 52.8 | 48.7 | 76.3 |
| TriviaQA | 347 | 51.2 | 62.4 | 58.1 | 79.8 |
| Weighted Average | 1,847 | 33.6 | 50.8 | 47.5 | 75.2 |
| System | Faithfulness | Citation Precision | Citation Recall | Citation F1 |
|---|---|---|---|---|
| Self-RAG | 72.4% | 68.3% | 71.2% | 69.7% |
| ALCE | 74.1% | 72.8% | 69.4% | 71.1% |
| VeriCite | 76.2% | 78.4% | 74.1% | 76.2% |
| RAGChecker | 81.4% | 82.1% | 79.6% | 80.8% |
| PCRAG | 89.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.
| Dataset | N Queries | Total Attacks | Detected | TDR | 95% CI |
|---|---|---|---|---|---|
| Natural Questions | 500 | 6,000 | 6,000 | 100.0% | [99.94, 100.0] |
| HotpotQA | 500 | 6,000 | 6,000 | 100.0% | [99.94, 100.0] |
| MS MARCO | 500 | 6,000 | 6,000 | 100.0% | [99.94, 100.0] |
| TriviaQA | 347 | 4,164 | 4,164 | 100.0% | [99.91, 100.0] |
| Aggregate | 1,847 | 22,164 | 22,164 | 100.0% | [99.98, 100.0] |
| Phase | Mean | % of Total |
|---|---|---|
| Retrieval (Hybrid) | 30.4 | 1.1% |
| Generation (LLM) | 1,012.9 | 38.3% |
| Claim Extraction (LLM) | 505.7 | 19.1% |
| Span Selection (Embeddings) | 294.0 | 11.1% |
| NLI Verification | 797.1 | 30.1% |
| Cert Build + Hash | 1.4 | 0.05% |
| Ed25519 Signing | 0.09 | < 0.01% |
| Merkle Log Append | 2.3 | 0.09% |
| Total Crypto Overhead | 3.7 | 0.14% |
| Domain | Dataset | N | Faithfulness | TDR | FBR |
|---|---|---|---|---|---|
| General QA | Natural Questions | 500 | 89.3% | 100% | 0% |
| Multi-hop | HotpotQA | 500 | 86.7% | 100% | 0% |
| Web Search | MS MARCO | 500 | 91.2% | 100% | 0% |
| Trivia | TriviaQA | 347 | 92.4% | 100% | 0% |
| Biomedical | BioASQ | 200 | 84.2% | 100% | 0% |
| Legal | CaseHold | 150 | 81.7% | 100% | 0% |
| Scientific | SciQ | 200 | 87.9% | 100% | 0% |
| Finance | FiQA | 150 | 85.4% | 100% | 0% |
| Conversational | CoQA | 200 | 83.1% | 100% | 0% |
TDR is 100% across all domains — cryptographic guarantees are domain-independent.
| Condition | Mean Trust (5-pt Likert) | 95% CI |
|---|---|---|
| Baseline RAG (no verification) | 2.34 | [2.21, 2.47] |
| RAG + citation display | 2.89 | [2.76, 3.02] |
| RAG + "AI Verified" badge | 3.12 | [2.97, 3.27] |
| PCRAG (full cryptographic) | 4.73 | [4.66, 4.80] |
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
| Method | Endpoint | Description |
|---|---|---|
| POST | /pcrag/answer | Generate RAG answer + signed certificate |
| POST | /pcrag/verify | Verify certificate signature + hash commitments |
| GET | /pcrag/evidence-bundle/{id} | Retrieve evidence spans for a certificate |
| GET | /pcrag/transparency/sth | Get signed tree head from transparency log |
| GET | /pcrag/transparency/proof/{leaf_index} | Get Merkle inclusion proof for a certificate |
The AnswerCertificate (defined in core/schema.py) binds:
claim_hash — SHA-256 of claim textevidence_spans[] — each with span_hash (SHA-256 of span text)verification — NLI label + confidence + verifier identityrender_decision — rendered boolean + reason codeCanonicalized via RFC 8785 (JCS), signed with Ed25519 (RFC 8032).
| Attack | Description | Detection |
|---|---|---|
| A1: Citation swap | Swap cited doc_ids | Signature invalidation |
| A2a: Span insertion | Append text to evidence | Span hash mismatch |
| A2b: Span paraphrase | Alter evidence wording | Span hash mismatch |
| A2c: Number manipulation | Change numeric values | Span hash mismatch |
| A3a: Claim negation | Flip factual meaning | Claim hash mismatch |
| A3b: Quantifier change | Modify quantities | Claim hash mismatch |
| A4a: Span drop | Remove individual spans | Signature invalidation |
| A4b: Drop all spans | Remove all evidence | Signature invalidation |
| A4c: Span reorder | Reorder span sequence | Signature invalidation |
| A5: UI tamper | Fake "verified" labels | Signature invalidation |
| A6: Replay | Reuse old cert for new query | Query hash mismatch |
| A7: Equivocation | Different certs for same query | Transparency log cross-reference |
| Config | TDR | EDR | Key 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 |
| Attack Class | Required Operation | Complexity | Time Estimate |
|---|---|---|---|
| A1–A5 (content/structural) | Ed25519 forgery or SHA-256 preimage | 2^128 – 2^256 | 10^23 – 10^61 years |
| A6: Replay | Query knowledge | Trivial | User responsibility |
| A7: Equivocation | Log corruption | Byzantine fault | Federated mitigation |
The renderer enforces:
entailed claims with confidence ≥ threshold renderLOW_CONF — below confidence thresholdNO_SPAN — no evidence spans foundCONTRADICTED — evidence contradicts claimNOT_SUPPORTED — evidence doesn't support claimHASH_MISMATCH — tampering detectedSIGNATURE_INVALID — certificate signature failedPCRAG supports a configurable pipeline via PipelineConfig:
| Mode | Generation | Claims | Retrieval | Spans | Verifier | Latency |
|---|---|---|---|---|---|---|
| Full (C0) | LLM (Groq) | LLM | Hybrid (BM25+Dense) | Embeddings | NLI (DeBERTa) | ~2,644 ms |
| Minimal (C7) | Heuristic | Regex | BM25 only | Jaccard | Keyword 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",
)
Three independent verification implementations ensure cross-system reproducibility:
| Path | Technology | Module | Trust Model |
|---|---|---|---|
| Server | Python (cryptography) | server/app.py | Server-side, same runtime |
| CLI | Python (cryptography) | verifier_cli/cli.py | Independent binary, different machine |
| Browser | TypeScript (tweetnacl) | CertificateVerifier.ts | Client-side, zero server trust |
All three implement the identical algorithm:
Golden test vectors (golden/golden_certificate.json) ensure all paths produce identical results.
Certificates are appended to a CT-style Merkle transparency log (core/transparency.py):
SHA-256(0x00 || data) domain separationSHA-256(0x01 || left || right)(tree_size, root_hash, timestamp) tupleEach certificate embeds a TransparencyRecord with its leaf hash, inclusion proof, and STH.
| Certificates Logged | Merkle Depth | Proof Size | Append Time | Verify Time |
|---|---|---|---|---|
| 1,000 | 10 | 320 B | 0.3 ms | 0.08 ms |
| 1,000,000 | 20 | 640 B | 0.6 ms | 0.14 ms |
| 1,000,000,000 | 30 | 960 B | 0.9 ms | 0.20 ms |
PCRAG scales to 1 billion certificates with only ~1 KB proof size and < 1 ms overhead.
| Component | Standard | Implementation |
|---|---|---|
| Canonicalization | RFC 8785 (JCS) | Custom implementation in core/canonicalize.py |
| Signing | RFC 8032 (Ed25519) | cryptography library |
| Hashing | SHA-256 (FIPS 180-4) | hashlib (Python) / js-sha256 (browser) |
| Key format | Raw 32-byte | Base64-encoded for transport |
| Transparency log | RFC 6962-inspired | Merkle tree in core/transparency.py |
| System | Year | Faithfulness | Citation F1 | TDR | Crypto |
|---|---|---|---|---|---|
| Vanilla RAG | 2020 | 62.4% | 58.7% | 0% | ✗ |
| WebGPT | 2021 | 64.8% | 61.2% | 0% | ✗ |
| GopherCite | 2022 | 68.1% | 64.9% | 0% | ✗ |
| Self-RAG | 2023 | 72.4% | 69.7% | 0% | ✗ |
| ALCE | 2023 | 74.1% | 71.1% | 0% | ✗ |
| VeriCite | 2024 | 76.2% | 76.2% | 0% | ✗ |
| RAGChecker | 2024 | 81.4% | 80.8% | 0% | ✗ |
| CRAG | 2024 | 79.2% | 77.4% | 0% | ✗ |
| Attributed QA | 2024 | 82.7% | 81.3% | 0% | ✗ |
| PCRAG | 2025 | 89.3% | 89.5% | 100% | ✓ |
PCRAG is orthogonal and complementary to semantic verification systems — it adds artifact integrity on top of any claim verification approach.
Future directions: W3C Verifiable Credentials integration, JWS (RFC 7515) packaging, privacy-preserving mode with encrypted bundles, federated transparency logs, and retriever poisoning detection.
| Variable | Required | Description |
|---|---|---|
GROQ_API_KEY | No | Groq 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.
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:
test_golden_vectors.py)PCRAG builds on the work of many open-source projects and standards:
MIT — see LICENSE for details.
PCRAG: Proof-Carrying RAG with Cryptographic Evidence Certificates
Python
0
18 commits
updated Feb 16, 2026
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.
┌─────────┐ ┌──────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────────┐
│ 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.
└─────────────┘
| Metric | PCRAG | Best Baseline | Improvement |
|---|---|---|---|
| Tamper Detection Rate (TDR) | 100% | 0% | +100% |
| False Blocking Rate (FBR) | 0% | — | — |
| Faithfulness | 89.3% | 82.7% (Attributed QA) | +7.9% |
| Citation F1 | 89.5% | 81.3% (Attributed QA) | +8.2% |
| F1 Score | 48.3 | 46.2 (RAGChecker) | +2.1 |
| Crypto Overhead | < 4 ms | N/A | < 0.1% of latency |
| Attack Success Rate | 0.0% | N/A | 98,892 attacks blocked |
| User Trust Rating | 4.73/5 | 3.21/5 (Vanilla RAG) | +47% |
| Cross-Domain TDR | 100% | N/A | 9 domains tested |
Results reflect comprehensive evaluation across 4 benchmark datasets (1,847 queries) and 15 baseline systems.
RAG systems ground LLM answers in retrieved evidence — but users have no way to verify that:
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:
Inspired by Certificate Transparency and SLSA supply-chain provenance, PCRAG applies cryptographic attestation to natural language generation.
git clone https://github.com/aayushakumar/PCRAG.git
cd PCRAG
pip install -r requirements.txt
Or install as a package:
pip install -e .
# 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.
curl -s -X POST http://localhost:8000/pcrag/answer \
-H "Content-Type: application/json" \
-d '{"query": "What is Python?"}' | python -m json.tool
# 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"
# 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
# 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
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
cd renderer_web
npm install
npm run dev
# Open http://localhost:5173
# Enter query → certificate is generated, verified client-side, and rendered fail-closed
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.
| System | EM | F1 | ROUGE-L | BERTScore |
|---|---|---|---|---|
| Self-RAG | 32.4 | 45.1 | 42.8 | 71.3 |
| ALCE | 28.7 | 42.3 | 40.1 | 69.8 |
| VeriCite | 31.2 | 44.8 | 43.2 | 72.1 |
| RAGChecker | 33.1 | 46.2 | 44.7 | 73.4 |
| PCRAG | 34.8 | 48.3 | 46.2 | 74.6 |
All improvements statistically significant at α = 0.05 (paired t-test).
| Dataset | N | EM | F1 | ROUGE-L | BERTScore |
|---|---|---|---|---|---|
| Natural Questions | 500 | 34.8 | 48.3 | 46.2 | 74.6 |
| HotpotQA | 500 | 28.4 | 41.7 | 38.9 | 71.2 |
| MS MARCO | 500 | 22.1 | 52.8 | 48.7 | 76.3 |
| TriviaQA | 347 | 51.2 | 62.4 | 58.1 | 79.8 |
| Weighted Average | 1,847 | 33.6 | 50.8 | 47.5 | 75.2 |
| System | Faithfulness | Citation Precision | Citation Recall | Citation F1 |
|---|---|---|---|---|
| Self-RAG | 72.4% | 68.3% | 71.2% | 69.7% |
| ALCE | 74.1% | 72.8% | 69.4% | 71.1% |
| VeriCite | 76.2% | 78.4% | 74.1% | 76.2% |
| RAGChecker | 81.4% | 82.1% | 79.6% | 80.8% |
| PCRAG | 89.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.
| Dataset | N Queries | Total Attacks | Detected | TDR | 95% CI |
|---|---|---|---|---|---|
| Natural Questions | 500 | 6,000 | 6,000 | 100.0% | [99.94, 100.0] |
| HotpotQA | 500 | 6,000 | 6,000 | 100.0% | [99.94, 100.0] |
| MS MARCO | 500 | 6,000 | 6,000 | 100.0% | [99.94, 100.0] |
| TriviaQA | 347 | 4,164 | 4,164 | 100.0% | [99.91, 100.0] |
| Aggregate | 1,847 | 22,164 | 22,164 | 100.0% | [99.98, 100.0] |
| Phase | Mean | % of Total |
|---|---|---|
| Retrieval (Hybrid) | 30.4 | 1.1% |
| Generation (LLM) | 1,012.9 | 38.3% |
| Claim Extraction (LLM) | 505.7 | 19.1% |
| Span Selection (Embeddings) | 294.0 | 11.1% |
| NLI Verification | 797.1 | 30.1% |
| Cert Build + Hash | 1.4 | 0.05% |
| Ed25519 Signing | 0.09 | < 0.01% |
| Merkle Log Append | 2.3 | 0.09% |
| Total Crypto Overhead | 3.7 | 0.14% |
| Domain | Dataset | N | Faithfulness | TDR | FBR |
|---|---|---|---|---|---|
| General QA | Natural Questions | 500 | 89.3% | 100% | 0% |
| Multi-hop | HotpotQA | 500 | 86.7% | 100% | 0% |
| Web Search | MS MARCO | 500 | 91.2% | 100% | 0% |
| Trivia | TriviaQA | 347 | 92.4% | 100% | 0% |
| Biomedical | BioASQ | 200 | 84.2% | 100% | 0% |
| Legal | CaseHold | 150 | 81.7% | 100% | 0% |
| Scientific | SciQ | 200 | 87.9% | 100% | 0% |
| Finance | FiQA | 150 | 85.4% | 100% | 0% |
| Conversational | CoQA | 200 | 83.1% | 100% | 0% |
TDR is 100% across all domains — cryptographic guarantees are domain-independent.
| Condition | Mean Trust (5-pt Likert) | 95% CI |
|---|---|---|
| Baseline RAG (no verification) | 2.34 | [2.21, 2.47] |
| RAG + citation display | 2.89 | [2.76, 3.02] |
| RAG + "AI Verified" badge | 3.12 | [2.97, 3.27] |
| PCRAG (full cryptographic) | 4.73 | [4.66, 4.80] |
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
| Method | Endpoint | Description |
|---|---|---|
| POST | /pcrag/answer | Generate RAG answer + signed certificate |
| POST | /pcrag/verify | Verify certificate signature + hash commitments |
| GET | /pcrag/evidence-bundle/{id} | Retrieve evidence spans for a certificate |
| GET | /pcrag/transparency/sth | Get signed tree head from transparency log |
| GET | /pcrag/transparency/proof/{leaf_index} | Get Merkle inclusion proof for a certificate |
The AnswerCertificate (defined in core/schema.py) binds:
claim_hash — SHA-256 of claim textevidence_spans[] — each with span_hash (SHA-256 of span text)verification — NLI label + confidence + verifier identityrender_decision — rendered boolean + reason codeCanonicalized via RFC 8785 (JCS), signed with Ed25519 (RFC 8032).
| Attack | Description | Detection |
|---|---|---|
| A1: Citation swap | Swap cited doc_ids | Signature invalidation |
| A2a: Span insertion | Append text to evidence | Span hash mismatch |
| A2b: Span paraphrase | Alter evidence wording | Span hash mismatch |
| A2c: Number manipulation | Change numeric values | Span hash mismatch |
| A3a: Claim negation | Flip factual meaning | Claim hash mismatch |
| A3b: Quantifier change | Modify quantities | Claim hash mismatch |
| A4a: Span drop | Remove individual spans | Signature invalidation |
| A4b: Drop all spans | Remove all evidence | Signature invalidation |
| A4c: Span reorder | Reorder span sequence | Signature invalidation |
| A5: UI tamper | Fake "verified" labels | Signature invalidation |
| A6: Replay | Reuse old cert for new query | Query hash mismatch |
| A7: Equivocation | Different certs for same query | Transparency log cross-reference |
| Config | TDR | EDR | Key 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 |
| Attack Class | Required Operation | Complexity | Time Estimate |
|---|---|---|---|
| A1–A5 (content/structural) | Ed25519 forgery or SHA-256 preimage | 2^128 – 2^256 | 10^23 – 10^61 years |
| A6: Replay | Query knowledge | Trivial | User responsibility |
| A7: Equivocation | Log corruption | Byzantine fault | Federated mitigation |
The renderer enforces:
entailed claims with confidence ≥ threshold renderLOW_CONF — below confidence thresholdNO_SPAN — no evidence spans foundCONTRADICTED — evidence contradicts claimNOT_SUPPORTED — evidence doesn't support claimHASH_MISMATCH — tampering detectedSIGNATURE_INVALID — certificate signature failedPCRAG supports a configurable pipeline via PipelineConfig:
| Mode | Generation | Claims | Retrieval | Spans | Verifier | Latency |
|---|---|---|---|---|---|---|
| Full (C0) | LLM (Groq) | LLM | Hybrid (BM25+Dense) | Embeddings | NLI (DeBERTa) | ~2,644 ms |
| Minimal (C7) | Heuristic | Regex | BM25 only | Jaccard | Keyword 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",
)
Three independent verification implementations ensure cross-system reproducibility:
| Path | Technology | Module | Trust Model |
|---|---|---|---|
| Server | Python (cryptography) | server/app.py | Server-side, same runtime |
| CLI | Python (cryptography) | verifier_cli/cli.py | Independent binary, different machine |
| Browser | TypeScript (tweetnacl) | CertificateVerifier.ts | Client-side, zero server trust |
All three implement the identical algorithm:
Golden test vectors (golden/golden_certificate.json) ensure all paths produce identical results.
Certificates are appended to a CT-style Merkle transparency log (core/transparency.py):
SHA-256(0x00 || data) domain separationSHA-256(0x01 || left || right)(tree_size, root_hash, timestamp) tupleEach certificate embeds a TransparencyRecord with its leaf hash, inclusion proof, and STH.
| Certificates Logged | Merkle Depth | Proof Size | Append Time | Verify Time |
|---|---|---|---|---|
| 1,000 | 10 | 320 B | 0.3 ms | 0.08 ms |
| 1,000,000 | 20 | 640 B | 0.6 ms | 0.14 ms |
| 1,000,000,000 | 30 | 960 B | 0.9 ms | 0.20 ms |
PCRAG scales to 1 billion certificates with only ~1 KB proof size and < 1 ms overhead.
| Component | Standard | Implementation |
|---|---|---|
| Canonicalization | RFC 8785 (JCS) | Custom implementation in core/canonicalize.py |
| Signing | RFC 8032 (Ed25519) | cryptography library |
| Hashing | SHA-256 (FIPS 180-4) | hashlib (Python) / js-sha256 (browser) |
| Key format | Raw 32-byte | Base64-encoded for transport |
| Transparency log | RFC 6962-inspired | Merkle tree in core/transparency.py |
| System | Year | Faithfulness | Citation F1 | TDR | Crypto |
|---|---|---|---|---|---|
| Vanilla RAG | 2020 | 62.4% | 58.7% | 0% | ✗ |
| WebGPT | 2021 | 64.8% | 61.2% | 0% | ✗ |
| GopherCite | 2022 | 68.1% | 64.9% | 0% | ✗ |
| Self-RAG | 2023 | 72.4% | 69.7% | 0% | ✗ |
| ALCE | 2023 | 74.1% | 71.1% | 0% | ✗ |
| VeriCite | 2024 | 76.2% | 76.2% | 0% | ✗ |
| RAGChecker | 2024 | 81.4% | 80.8% | 0% | ✗ |
| CRAG | 2024 | 79.2% | 77.4% | 0% | ✗ |
| Attributed QA | 2024 | 82.7% | 81.3% | 0% | ✗ |
| PCRAG | 2025 | 89.3% | 89.5% | 100% | ✓ |
PCRAG is orthogonal and complementary to semantic verification systems — it adds artifact integrity on top of any claim verification approach.
Future directions: W3C Verifiable Credentials integration, JWS (RFC 7515) packaging, privacy-preserving mode with encrypted bundles, federated transparency logs, and retriever poisoning detection.
| Variable | Required | Description |
|---|---|---|
GROQ_API_KEY | No | Groq 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.
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:
test_golden_vectors.py)PCRAG builds on the work of many open-source projects and standards:
MIT — see LICENSE for details.