Building a Verifiable Receipt System with AI and Rust
Learn how to build a tamper-evident receipt system using Rust for cryptographic integrity and AI for automated validation. This article covers architecture, code, and trade-offs for production-grade systems.
The Problem & Industry Shift
In the digital age, receipts are no longer just paper slips. They are critical for expense reporting, tax compliance, and warranty claims. However, traditional receipts are easy to forge or alter, leading to fraud and disputes. The industry is shifting towards verifiable receipts that use cryptographic signatures and AI to ensure authenticity and integrity.
Previous approaches relied on simple PDFs or images, which could be edited with basic tools. Even with digital signatures, the validation process was manual and error-prone. The need for automated, tamper-evident receipts has grown with the rise of e-commerce and remote work.
This article presents a system that combines Rust's performance and safety for cryptographic operations with AI for automated content validation. The result is a receipt that is both cryptographically verifiable and semantically validated.
Architecture & Core Mechanics
The system consists of three main components:
- Receipt Generator: Creates a receipt payload, signs it with a private key, and outputs a JSON with a signature.
- AI Validator: Uses a machine learning model to extract and validate key fields (e.g., total amount, vendor) from the receipt image or text.
- Verifier: Checks the cryptographic signature and optionally cross-validates with AI-extracted data.
Below is a high-level data flow:
[Receipt Data] -> [Hash] -> [Sign with Private Key] -> [Receipt JSON]
|
v
[Receipt JSON] -> [Verify Signature] -> [AI Extract Fields] -> [Compare with Expected] -> [Valid/Invalid]
The core idea is to separate concerns: Rust handles the cryptographic integrity, while AI handles the semantic understanding. This allows each part to be optimized independently.
Production Code Example
We'll use Rust for the signing/verification and Python for the AI part, but for brevity, we'll show a Rust example that includes a simple AI-like rule-based validation.
// Cargo.toml: ed25519-dalek = "2", serde = { version = "1", features = ["derive"] }, serde_json = "1", sha2 = "0.10"
use ed25519_dalek::{Signer, Verifier, SigningKey, VerifyingKey, Signature};
use sha2::{Sha256, Digest};
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
struct Receipt {
vendor: String,
amount: f64,
date: String,
items: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct SignedReceipt {
receipt: Receipt,
signature: String,
}
fn hash_receipt(receipt: &Receipt) -> Vec<u8> {
let serialized = serde_json::to_vec(receipt).expect("serialize");
let mut hasher = Sha256::new();
hasher.update(serialized);
hasher.finalize().to_vec()
}
fn sign_receipt(receipt: &Receipt, signing_key: &SigningKey) -> String {
let hash = hash_receipt(receipt);
let signature = signing_key.sign(&hash);
hex::encode(signature.to_bytes())
}
fn verify_signature(signed: &SignedReceipt, verifying_key: &VerifyingKey) -> bool {
let hash = hash_receipt(&signed.receipt);
let sig_bytes = hex::decode(&signed.signature).expect("hex decode");
let signature = Signature::from_bytes(&sig_bytes).expect("signature");
verifying_key.verify(&hash, &signature).is_ok()
}
// AI-like validation: rule-based for demo
fn ai_validate(receipt: &Receipt) -> bool {
// In production, this would call a trained model (e.g., using ONNX Runtime)
// to extract fields from an image or text and compare.
// Here we simulate by checking amount > 0 and vendor not empty.
receipt.amount > 0.0 && !receipt.vendor.is_empty()
}
fn main() {
// Generate keys (in production, store securely)
let signing_key = SigningKey::generate(&mut rand::rngs::OsRng);
let verifying_key = signing_key.verifying_key();
// Create a receipt
let receipt = Receipt {
vendor: "ACME Corp".to_string(),
amount: 123.45,
date: "2024-01-01".to_string(),
items: vec!["Widget".to_string()],
};
// Sign
let signature = sign_receipt(&receipt, &signing_key);
let signed = SignedReceipt { receipt, signature };
// Verify signature
let sig_valid = verify_signature(&signed, &verifying_key);
println!("Signature valid: {}", sig_valid);
// AI validation
let ai_valid = ai_validate(&signed.receipt);
println!("AI validation passed: {}", ai_valid);
// In production, you would also compare AI-extracted fields with the signed data.
}
Critical engineering decisions:
- Use Ed25519 for fast, secure signatures.
- Hash the canonical JSON to avoid ambiguity.
- Separate AI validation from cryptographic verification to allow independent scaling.
Performance, Cost & Trade-offs
- Latency: Signing and verification are sub-millisecond with Ed25519. AI inference adds 100-500ms depending on model complexity.
- Accuracy: AI models can misread receipts; use confidence thresholds and human review for edge cases.
- Cost: Running AI models on CPU is cheap but slower; GPU instances increase cost but reduce latency.
- Security: Private keys must be stored securely (e.g., HSM). Signature verification is only as secure as the key management.
- Trade-off: Cryptographic verification ensures data hasn't been tampered with, but it doesn't guarantee the data is semantically correct. AI fills that gap but introduces its own errors.
Actionable Checklist / Summary
When adopting a verifiable receipt system in production:
- Define the receipt schema and use canonical JSON for hashing.
- Use Ed25519 for signing and verification; store keys in a secure vault.
- Implement AI validation with a model trained on your receipt formats; include confidence scores.
- Combine both checks: reject receipts that fail signature OR AI validation with low confidence.
- Log all verification attempts for auditability.
- Plan for key rotation and revocation.
- Test with adversarial examples (e.g., altered amounts, forged signatures).