Cornerstone Engineering Monograph · UK Patent Pending
Zero-Knowledge LLM Ingress: Luhn & NHS Modulus-11 Tokenisation with PIIVault and Scoped Detokenise[T]
Inside Blockstate's PIIVault: checksum-verified Luhn modulo-10 and NHS Modulus-11 detectors, keyed HMAC-SHA256 deduplication, AES-256-GCM revision-bound AAD encryption, and volatile Detokenise[T] unwrapping.
1. Why Regex Masking & Post-Hoc Log Scrubbing Fail in Regulated AI
When a patient messages a clinical triage assistant or a banking customer disputes a card transaction, their utterance frequently contains high-sensitivity identifiers inside unstructured prose:
"My NHS number is 485 777 3456, my postcode is M1 1AG, and I have had a fever for two days."
Under UK GDPR, NHS DCB0129, and US HIPAA Safe Harbor, transmitting raw patient identifiers or primary account numbers (PANs) to external commercial LLM APIs, or persisting them inside application trace logs, creates severe data protection liability.
Standard masking approaches suffer from three engineering flaws:
- Naive Regex False Positives: A naive 10-digit regular expression matches ordinary reference numbers, timestamps, and order IDs, corrupting non-PII data before the model can read it.
- Broken Downstream Validation & Tool Execution: If you replace
"485 777 3456"with"[REDACTED]", your internal Electronic Health Record (EHR) lookup tool (lookup_patient_ehr_record) and your Pydantic@field_validatormethods can no longer verify the patient's registration! - Unauthenticated Token Maps: Storing a plaintext lookup dictionary in session state allows cross-session replay or database snapshot exposure.
2. Checksum-Verified Detection & Overlap Resolution (pii_patterns.py)
Blockstate's PIIVault (src/blockstate/security/vault.py and pii_patterns.py, Claims 10 & 11) executes at the outermost ingress boundary before Pass 1 slot extraction:
- Luhn Modulo-10 Checksum Verification: Candidate 13-to-19-digit payment card spans are stripped of separators and verified against the Luhn modulo-10 algorithm before classification as
CARD. - NHS Modulus-11 Check-Digit Verification: Candidate 10-digit NHS numbers apply the official NHS Modulus-11 weights ($10, 9, 8, 7, 6, 5, 4, 3, 2$) to the first nine digits, compute $11 - (\sum w_i d_i \bmod 11)$, reject remainder
10as invalid, and verify equality with the tenth check digit. - Longest-Span & Priority-Rank Overlap Resolution: When multiple detectors match overlapping character spans, the resolver orders matches deterministically by longest character span and explicit category priority rank, while protecting any pre-existing
<VAULT:...>tokens in the input from double-tokenisation or partial-token prompt injection.
flowchart TD
subgraph Ingress["1. Ingress Sanitisation (vault.py)"]
IN["Raw Utterance:<br/>'My NHS number is 485 777 3456, postcode M1 1AG'"] --> DET["Checksum & Pattern Detectors<br/>(NHS Mod-11, Luhn Mod-10, NI, Postcode)"]
DET --> OVL["Longest-Span + Priority-Rank Overlap Resolver<br/>(Protects existing <VAULT:...> spans)"]
OVL --> HMAC["Keyed HMAC-SHA256 Session Deduplication<br/>Allocate <VAULT:NHS_NUMBER_01>, <VAULT:POSTCODE_01>"]
HMAC --> AEAD["Encrypt Token Map with AES-256-GCM<br/>12-Byte Nonce + AAD = session_id:schema_version:revision_id"]
end
subgraph RuntimeScope["2. Volatile-Memory Unwrapping Boundaries"]
AEAD --> VAL["wrap_detokenising_validator<br/>Unwrap in Volatile RAM -> Validate -> Re-bind Token & Scrub Exceptions"]
AEAD --> TOOL["ToolGatekeeper Detokenise[T]<br/>Unwrap ONLY Annotated Parameters -> Execute -> Re-sanitise Output"]
end
3. Keyed HMAC-SHA256 Deduplication & Revision-Bound AES-256-GCM
When a sensitive span is confirmed:
- Deterministic Session Deduplication:
PIIVaultcomputes a keyedHMAC-SHA256digest of the normalised cleartext value using a session-scoped key. If the user repeats their NHS number or postcode in Turn 3, it resolves deterministically to<VAULT:NHS_NUMBER_01>rather than allocating<VAULT:NHS_NUMBER_02>. AES-256-GCMwith Colon-Separated AAD: The mapping between<VAULT:CATEGORY_NN>tokens and cleartext is encrypted at rest usingAES-256-GCMwith a fresh 12-byte random nonce per commit and Associated Authenticated Data (AAD) formatted strictly as:
$$\text{AAD} = \texttt{session\_id:schema\_version:revision\_id}$$
Binding the ciphertext to the exact session ID, schema version, and monotonic CAS revision ID prevents an attacker from splicing an encrypted vault blob into another session or rolling back state revisions.
4. Scoped Volatile-RAM Unwrapping: Detokenise[T] & wrap_detokenising_validator
Throughout the LLM prompt context, PartialSlotModel storage, OpenTelemetry spans, and the TurnLedger, the slot value remains literally "<VAULT:NHS_NUMBER_01>". Cleartext only exists momentarily in volatile RAM at two explicitly authorised boundaries:
Boundary A: Tool Execution via Detokenise[T] (Claim 4)
Tools declare whether a parameter requires cleartext using the Detokenise[T] type annotation:
from pydantic import BaseModel, Field
from blockstate import Agent, Stage, Transition, Detokenise
class PatientIntakeSlots(BaseModel):
nhs_number: str = Field(description="Tokenised NHS identifier (<VAULT:NHS_NUMBER_NN>)")
postcode: str = Field(description="Tokenised patient postcode (<VAULT:POSTCODE_NN>)")
primary_symptom: str = Field(description="Clinical symptom summary")
async def lookup_patient_ehr_record(
nhs_number: Detokenise[str],
postcode: Detokenise[str],
) -> dict[str, str]:
# ToolGatekeeper unwraps <VAULT:NHS_NUMBER_01> -> "485 777 3456"
# strictly inside volatile memory for this call, and re-sanitises the return dict!
return {"registration_status": "ACTIVE", "gp_practice_code": "Y00124"}
Boundary B: Validator Execution via wrap_detokenising_validator (Claim 9)
When a Pydantic @field_validator inspects a Vaulted[T] field, wrap_detokenising_validator unwraps the token in volatile memory immediately before calling the validator, re-binds the <VAULT:...> token immediately upon return, and scrubs any cleartext value from exception messages if validation fails.
5. Verifying Zero PII Leakage with blockstate trace
At any point, compliance and security officers can inspect the resulting RFC 8785 JCS SHA-256 TurnLedger chain:
$ blockstate trace --session 0192ce18-7b40-7000-8000-4a91f0012c9a ✔ Cryptographic Hash Chain Verified (2/2 records intact, RFC 8785 JCS canonical SHA-256, 0 raw PII tokens leaked)
Statutory Notice: Blockstate is UK Patent Pending ("Process execution control system", UKIPO priority filing 28 October 2026, UK Patents Act 1977 s.62(1)) and distributed under a Source-Available Dual Licence (LICENSE & PATENTS.md).
MappingProxyType tool gatekeeper, and TaskStack digression call stack live in the Blockstate Signal Box Simulator. Distributed under the Blockstate Source-Available Dual Licence (Free £0 for local dev, open-source CI/CD verification via blockstate verify, and 90-day internal enterprise staging evaluation; paid commercial licence for live production).