openpay/
compliance/ PCI-DSS readiness package (scope-map,
pci-zero-architecture, hsm-kms-guidance,
qsa-checklist), citing PCI-DSS v4.0.1
requirements. (#1)
examples/pci-zero/ Runnable example demonstrating a deployment
where merchant code never touches a PAN. (#1)
crates/op-core/ Tokenized<Card> typestate + NetworkToken
+ CardNetwork + Cryptogram. (#2)
crates/op-rails-card/network_token/
NetworkTokenProvider trait + VTS adapter
+ MDES adapter, with deterministic stubs
behind a 'live' feature flag. (#2)
crates/op-driver-sdk/conformance/network_token.rs
Conformance harness for third-party
NetworkTokenProvider implementations. (#2)
crates/op-cli/src/ledger/ 'openpay ledger as-of' subcommand for
bi-temporal queries. (#3)
crates/op-orchestrator/src/telemetry.rs
OpenTelemetry span emission per
Payment<S> transition with semantic-
conventions tags. (#3)
docs/observability/ Grafana dashboard JSON + wiring guide
for the bi-temporal ledger lens. (#3)
smart-byte/
smart-byte-rs/ Rust reference implementation: envelope
encode/decode (canonical CBOR + BLAKE3
content addressing), Ed25519 sign +
verify, lockstep BFT commit MVP, Iroh
transport scaffold, CLI. (#4)
spec/identity_and_key_rotation.md
Adopts KERI SAIDs + key-event log
format; explicitly does not adopt ACDC
chaining wholesale. (#5)
conformance/v1/ 21 test vectors covering canonicalisation,
SAID computation, signature verification,
lockstep commit, gossip propagation.
CI gate enforces spec/vector coupling. (#6)
eoc/
eoc-rs/ Rust + WASM reference implementation:
4-stage cascade (cache -> kv -> graph ->
neural), joule counters via RAPL / NVML /
powermetrics, CLI, wasm-bindgen surface.
18 tests pass. (#7)
eoc-bench/ Benchmark harness, MT-Bench and Router
case sets, 'joules per MT-Bench point'
metric, 'eoc-bench run | compare' CLI. (#8)
standards/sci-for-ai-submission.md
Submission proposal mapping EOC's
receipt schema to GSF SCI-for-AI. (#9)
cookbook/ HuggingFace-style cookbook (markdown +
runnable Python) demonstrating EOC's
cascade with HF AI Energy Score. (#9)
docs/bench-v0.md Reproducibility doc for eoc-bench.
Workspace state:
- openpay clippy -- -D warnings: clean across all touched crates
- smart-byte-rs cargo test --workspace + clippy: clean
- eoc-rs cargo test --workspace + clippy: clean (18 tests)
- eoc-bench cargo check --workspace + clippy + 15 tests: clean
Rust 1.95.0 edition 2024 pinned at the open-standards root via
rust-toolchain.toml; every workspace inherits.
Closes #1 #2 #3 #4 #5 #6 #7 #8 #9.
11 KiB
PCI-Zero Architecture
A deployment topology where merchant application servers never see a primary account number (PAN) and therefore qualify for SAQ-A or SAQ-A-EP rather than SAQ-D. The cardholder-data environment (CDE) collapses to a single tier — the vault — and every other OpenPay component runs outside it.
The runnable counterpart of this document is examples/pci-zero. That binary wires the same components in-process so the data-flow is auditable as a single Rust program; the deployment topology described here splits those components across hosts and networks.
Topology
┌────────────────────────────────────────────────────────────────────────┐
│ Customer browser / device │
│ │
│ • collection-iframe.js (served from vault.example.com domain) │
│ • TLS 1.3 to vault host (cert-pinned) │
│ • PAN entry happens here; merchant JS bundle CANNOT read the iframe │
│ (Same-Origin Policy isolates the iframe document) │
└────────────────────────────────────────────────────────────────────────┘
│ │
│ raw PAN (TLS 1.3) │ window.postMessage(VaultRef)
▼ ▼
┌──────────────────────────────────┐ ┌──────────────────────────────────┐
│ CDE network segment │ │ Merchant application network │
│ ──────────────────────────────── │ │ ──────────────────────────────── │
│ │ │ │
│ ┌────────────────────────────┐ │ │ ┌────────────────────────────┐ │
│ │ Vault service │ │ │ │ Merchant app server │ │
│ │ • op-vault impl │ │ │ │ • op-server │ │
│ │ • mTLS-fronted HTTP API │ │ │ │ • op-orchestrator │ │
│ │ • detokenize/tokenize/ │ │ │ │ • op-rails-card driver │ │
│ │ delete/exists │ │ │ │ • op-ledger / op-graph │ │
│ │ │◄─┼────┼──┤ stores │ │
│ │ Holds: encrypted PAN + │ │mTLS│ │ │ │
│ │ pci-scope build │ │ │ │ Holds: VaultRef tokens │ │
│ └──────────────┬─────────────┘ │ │ │ NEVER holds: raw PAN │ │
│ │ │ │ │ Build: pci-scope = OFF │ │
│ ▼ unwrap DEK │ │ └──────────────────────────────┘ │
│ ┌────────────────────────────┐ │ └──────────────────────────────────┘
│ │ KMS / HSM │ │ │
│ │ • AWS KMS / GCP KMS / │ │ │ Vault::detokenize
│ │ HashiCorp Vault Transit │ │ │ via mTLS, only at
│ │ / Fortanix DSM │ │ │ acquirer submit time
│ │ • FIPS 140-2 L3 or L4 │ │ ▼
│ │ • Holds: KEKs only │ │ ┌──────────────────────────────────┐
│ └────────────────────────────┘ │ │ Acquirer (Hyperswitch / direct) │
│ │ │ • PCI-DSS Level 1 service │
└──────────────────────────────────┘ │ provider; their scope, not │
▲ │ ours, once PAN is posted │
│ └──────────────────────────────────┘
│ Vault::detokenize (mTLS)
│ initiated by op-rails-card during authorize()
└──────────────────────────────────────────────────────────────
Trust boundaries
-
Browser ↔ Vault. The collection iframe runs from a domain controlled by the vault, not the merchant. The merchant's outer page receives a
VaultRefviawindow.postMessage; the merchant's JavaScript bundle cannot read the iframe document and so cannot read the PAN. This is the same pattern Stripe Elements, Adyen Components, and Braintree Drop-in use to keep merchants on SAQ-A. -
Merchant app server ↔ Vault service. Only at acquirer submit time, the merchant's
op-rails-carddriver code callsVault::detokenize(VaultRef)over a mutually authenticated TLS connection to the vault service. The detokenizedCardDatais serialised straight into the acquirer payload and the local Rust value drops +Zeroizes before the function returns. -
Vault service ↔ KMS / HSM. The vault never holds raw key material in memory long-term. The data-encryption key (DEK) used to encrypt each tokenized PAN is wrapped under a key-encryption key (KEK) held by the KMS / HSM. On
detokenize, the vault calls the KMS to unwrap the DEK, decrypts the ciphertext, and immediately zeroises the DEK. Pattern matches AWS KMS envelope encryption and HashiCorp Vault Transit secret engine. -
Vault service ↔ acquirer. The vault may post directly to the acquirer ("network token" pattern with Visa Token Service / Mastercard MDES) or hand
CardDataback to the merchant app server for a single submit call. The example uses the latter; both designs are valid, and the second still keeps the merchant on the connected-to-scope side because the merchant process holdsCardDatafor the duration of the acquirer HTTP round-trip and then drops it.
PCI-DSS v4.0.1 requirements satisfied by this topology
| Req. | Title | How this topology satisfies |
|---|---|---|
| 1.4 | Boundary security between trusted and untrusted networks | CDE network segment terminates at the vault host. Inbound rules: mTLS from app servers, TLS 1.3 from browsers (cert-pinned). No other ports. |
| 2.2.1 | Configuration standards | Vault host runs a single binary built with --features op-vault/<backend> plus --features op-core/pci-scope. No general-purpose OS services. |
| 3.2.1 | Account data is not stored after authorization unless there is a legitimate business need | The merchant app server does not store PAN. Card-on-file goes into the vault under TokenizationPolicy::card_on_file(); everything downstream sees only VaultRef. |
| 3.3.1 | SAD is not stored after authorization | EMV Track 2 equivalent never persists past the op-emv parse step; the parsed blob is forwarded to the acquirer encrypted and not retained. |
| 3.5.1 | PAN is rendered unreadable wherever it is stored | Vault stores AES-256-GCM-SIV ciphertext (RFC 8452, misuse-resistant). The DEK is wrapped by KMS-held KEK. |
| 3.6.1 | Cryptographic keys are managed throughout their lifecycle | KMS / HSM (AWS KMS, GCP KMS, HashiCorp Vault, Fortanix DSM) provides rotation, access control, and audit logging on the KEK. See hsm-kms-guidance.md. |
| 3.7.1 | Key-management policies and procedures are documented | Operators document KEK rotation schedule (typical: 365 days for KEKs, 90 days for DEKs), separation of duties (KMS admin ≠ vault admin), and emergency-rotation runbook. |
| 4.2.1 | Strong cryptography during transmission over open / public networks | TLS 1.3 between browser and vault; mTLS between merchant app server and vault; TLS 1.2+ between vault and acquirer (acquirer's requirement). |
| 6.2.4 | Engineering practices to prevent or mitigate common software attacks | Money is integer minor units; no f64 floats. Payment<S> is a typestate machine; refund-before-capture is a compile error. CardData has Zeroize + ZeroizeOnDrop. Vault trait does not distinguish "not found" from "auth failed" (no oracle). |
| 8.3.1 | Strong authentication for all access into the CDE | Vault service requires mTLS for app-server callers (workload identity) and operator MFA for human admins. |
| 10.2.1 | Audit logs capture all individual user accesses to cardholder data | Vault emits a structured tracing event on every tokenize / detokenize / delete, with the calling workload identity, timestamp, VaultRef, and outcome. Logs ship to a write-once audit store. |
| 11.4.1 | External and internal penetration testing | Performed against the CDE segment (the vault) and the connected-to-scope boundary (the app server). Out-of-scope crates do not require dedicated pen-testing. |
What this topology does not cover
- EMV tap. If the device is a Linux kiosk with a tap reader (see
examples/kiosk-linux), the EMV blob from the reader's secure element is its own SAD-handling concern. Use a P2PE-validated reader so the EMV path stays SAD-free between the chip and the acquirer. - Stored card-on-file. Card-on-file (recurring billing) requires the vault to persist long-lived
VaultRef → ciphertextmappings. The vault becomes a long-running database; treat as Req. 3.5 storage scope. - Refunds. A refund references the original
psp_payment_id, not the PAN, so refunds run entirely outside the CDE.
Pointer to runnable example
examples/pci-zero/ ships a single-process Rust binary that demonstrates the same data-flow in code: a fake browser submits PAN to an in-process vault, the vault returns a VaultRef, the orchestrator runs the intent through a card rail, and the rail driver calls Vault::detokenize once at acquirer submit time. The example asserts via type-level evidence (#![forbid(unsafe_code)], no RawPan import outside op_vault, no CardData value crossing process boundaries) that the merchant code path never holds raw PAN.