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.
10 KiB
HSM / KMS Integration Guidance
This document gives operator-facing guidance for plugging an HSM or cloud KMS into an OpenPay deployment. The pattern follows the EvmSigner trait in op-rails-crypto (crates/op-rails-crypto/src/signer.rs): the OpenPay code builds an unsigned operation, hands it to an operator-supplied trait, and lets the operator's chosen backend perform the privileged cryptography.
The KeyProvider adapter trait
OpenPay does not ship a KeyProvider trait in the workspace because the Vault trait already abstracts the entire storage-side concern. Operators implementing a vault against a KMS or HSM should follow the same shape as EvmSigner:
// Pattern, not shipped code — operators add this to their own vault crate.
pub trait KeyProvider: Send + Sync {
/// Encrypt a fresh DEK (data-encryption key) under the KEK.
fn wrap_dek(&self, plaintext_dek: &[u8; 32]) -> Result<Vec<u8>>;
/// Decrypt a previously wrapped DEK.
fn unwrap_dek(&self, wrapped_dek: &[u8]) -> Result<[u8; 32]>;
/// Rotate the KEK. Returns the new key version identifier.
fn rotate_kek(&self) -> Result<KeyVersion>;
/// Active KEK version, for emitting on audit log entries.
fn active_kek_version(&self) -> Result<KeyVersion>;
}
The operator's vault impl then performs envelope encryption: generate a 32-byte DEK with getrandom, encrypt the PAN with AES-256-GCM-SIV under the DEK, call KeyProvider::wrap_dek to wrap the DEK under the KEK, store (wrapped_dek, ciphertext, nonce, kek_version) keyed by VaultRef. On detokenize, the reverse.
The four backends below all fit this shape.
AWS KMS
Setup
- Create a customer-managed CMK in the AWS region hosting the vault. Use a symmetric
SYMMETRIC_DEFAULTkey for envelope encryption; multi-region keys are appropriate if the vault is multi-AZ. - Restrict the CMK key policy to the IAM role attached to the vault EC2 / ECS / EKS workload identity. Statement:
kms:Encrypt,kms:Decrypt,kms:GenerateDataKey,kms:DescribeKey. Denykms:ScheduleKeyDeletionandkms:DisableKeyto non-break-glass roles. - Enable CloudTrail data events on the CMK. Ship the events to a separate AWS account (the log archive account in a Control Tower setup) so vault admins cannot tamper with their own audit trail.
- The operator's
KeyProviderimpl uses the AWS SDK for Rust (aws-sdk-kms) and authenticates via IMDSv2 / IRSA / workload identity — never a long-lived access key.
Latency
Encrypt/Decryptover a 32-byte DEK: typically 5-15 ms intra-region, 95th percentile under 30 ms. AWS does not publish an SLA on KMS latency; budget 50 ms in your detokenize path.- For high-throughput vaults, use the AWS KMS Data Key Caching pattern: cache unwrapped DEKs in memory for up to 60 seconds, bounded by an LRU cache of e.g. 1024 entries. This keeps amortised KMS cost at one call per minute per hot DEK.
Key rotation
- Enable AWS-managed annual KEK rotation (
kms:EnableKeyRotation). AWS retains every prior key version internally;Decryptautomatically uses the right version. No application changes required. - For forced rotation (suspected compromise), generate a new CMK, re-wrap every DEK by reading each ciphertext, decrypting under the old CMK, re-wrapping under the new CMK, writing back, and finally scheduling the old CMK for deletion (30-day window).
Audit log requirements
Each wrap_dek / unwrap_dek call must produce a CloudTrail entry containing the calling principal, the source IP (the vault host), the CMK ARN, the encryption context (operator should pass {"vault_ref": "<token>", "tenant": "<id>"} so the audit log identifies which PAN was touched), and the timestamp. Ship CloudTrail logs to S3 + CloudWatch Logs; retain for at least one year (PCI-DSS Req. 10.5.1).
GCP KMS (Cloud KMS)
Setup
- Create a key ring in the region hosting the vault. Create a
SOFTWAREorHSMprotection-level key inside it. For PCI scope chooseHSM(FIPS 140-2 Level 3). - Bind the vault workload's Google service account to
roles/cloudkms.cryptoKeyEncrypterDecrypteron the key. Do not grantcloudkms.cryptoKeys.createorcloudkms.cryptoKeys.destroyto the workload. - Enable Cloud Audit Logs (Admin Activity and Data Access) on the key ring. Sink to a separate BigQuery dataset or Cloud Storage bucket in a different project.
- Authenticate the vault workload via Workload Identity Federation (GKE) or the metadata server (GCE) — never a downloaded JSON key file.
Latency
Encrypt/Decrypt: 10-30 ms typical, 100 ms 99th percentile. HSM-protected keys add ~10 ms vs software keys. Budget 100 ms in the detokenize path.- Cloud KMS does not have a published data-key caching SDK pattern; implement caching identically to the AWS KMS guidance (LRU + 60-second TTL).
Key rotation
- Set
rotationPeriod = 7776000s(90 days) on the key. Cloud KMS creates a new primary key version automatically; older versions remain decryptable until explicitly destroyed. - For forced rotation, create a new key version manually (
gcloud kms keys versions create), wait for it to become primary, and then run the re-wrap loop.
Audit log requirements
Cloud Audit Logs record both the call and the encryption context (Cloud KMS calls it additional authenticated data; pass the same {"vault_ref": ..., "tenant": ...} as for AWS). Retain for one year minimum; PCI-DSS Req. 10.5.1.
HashiCorp Vault (Transit secret engine)
Setup
- Deploy HashiCorp Vault in HA mode (Raft storage backend, 5-node quorum). Enable the Transit secret engine at a mount path such as
transit-openpay/. - Create a key with
vault write -f transit-openpay/keys/openpay-cde type=aes256-gcm96 derived=true exportable=false.derived=truerequires the caller to supply a per-VaultRefcontext (giving you per-token key derivation for free);exportable=falseensures the key never leaves the Vault server. - Create a policy granting
updateontransit-openpay/encrypt/openpay-cdeandtransit-openpay/decrypt/openpay-cde, and deny everything else. - Authenticate the OpenPay vault workload via the AppRole, Kubernetes, or AWS-IAM auth method. Lease TTL: 1 hour, renew before expiry. No root tokens in production.
- Audit-log to a file device that ships to your SIEM. Enable the
enable_response_wrappingaudit option so token IDs are obfuscated in logs.
Latency
transit/encrypt/transit/decryptover 32 bytes: 2-8 ms typical against a same-VPC HashiCorp Vault cluster. Lower than cloud KMS because there is no AWS / GCP control-plane hop.- Budget 25 ms in the detokenize path.
Key rotation
vault write -f transit-openpay/keys/openpay-cde/rotatecreates a new key version. All future encrypts use the new version; decrypts of older ciphertexts continue to work because the ciphertext header carries the version number.- For forced rotation, also run
vault write transit-openpay/keys/openpay-cde/rewrapto mass-re-encrypt all data under the new version (HashiCorp Vault has a built-in rewrap API).
Audit log requirements
The Transit engine logs every encrypt / decrypt with the AppRole identity, source IP, request ID, and the context (derived=true makes this mandatory). Ship audit logs to a write-once store (S3 + Object Lock, or a dedicated log-archive instance). Retain one year minimum.
Fortanix DSM (Data Security Manager)
Setup
- Provision a Fortanix DSM cluster — SaaS or self-hosted. SaaS is FIPS 140-2 Level 3 certified; self-hosted on Intel SGX is FIPS 140-2 Level 3 (HSM mode).
- Create an App (service identity) for the OpenPay vault workload. Use App API key or certificate authentication; do not use username + password.
- Inside the App's group, create an AES-256 Security Object with operations
ENCRYPT,DECRYPT,WRAPKEY,UNWRAPKEYallowed andEXPORTdenied. - Configure quorum approvals if your operational model requires two-person control for KEK rotation or deletion.
- The Fortanix REST API or its PKCS#11 / KMIP interfaces all map to the
KeyProvidershape above; choose REST for cross-cloud portability, PKCS#11 for legacy compatibility.
Latency
- REST API to Fortanix SaaS: 20-50 ms typical (single-region), 100 ms 99th. Self-hosted on-prem on SGX: 5-15 ms.
- Budget 100 ms in the detokenize path for SaaS, 25 ms for self-hosted.
Key rotation
- DSM supports key versioning and automatic rotation policies. Set
rotation_policy.interval_days = 90on the Security Object. - Forced rotation: create a new key version manually, run the operator's re-wrap job. DSM keeps old versions decryptable per the Key state lifecycle (
Active → Suspended → Deactivated → Destroyed).
Audit log requirements
DSM emits an audit log entry for every Security Object operation, including the calling App, source IP, operation, and Security Object UUID. Forward logs via syslog to your SIEM; DSM supports CEF and JSON formats. Retain one year minimum.
Cross-provider comparison
| Provider | FIPS level | Typical latency | Caching pattern | Rotation cadence | Auth |
|---|---|---|---|---|---|
| AWS KMS | 140-2 L3 (HSM-backed CMKs) | 5-15 ms | LRU + 60 s TTL | Annual auto, manual on-demand | IAM role |
| GCP KMS | 140-2 L3 (HSM tier) | 10-30 ms | LRU + 60 s TTL | 90-day auto | Workload Identity |
| HashiCorp Vault Transit | 140-2 L1 (default) / L2 (with HSM auto-unseal) | 2-8 ms | not needed (already fast) | Manual, mass-rewrap supported | AppRole / Kubernetes / IAM |
| Fortanix DSM | 140-2 L3 (SGX) | 20-50 ms (SaaS) / 5-15 ms (self-hosted) | LRU + 60 s TTL | 90-day auto, quorum-gated manual | API key / cert |
Common pitfalls
- Caching unwrapped DEKs beyond their TTL. PCI-DSS Req. 3.7.1 expects DEKs to live no longer than necessary. Bound the cache at 60 seconds.
- Pinning the wrong KEK version into ciphertext metadata. Always record
kek_versionalongside the wrapped DEK; mass-rewrap operations need it. - Logging encryption context that contains the PAN. The encryption context becomes part of CloudTrail / Cloud Audit / Vault audit logs, which are not CDE-scoped storage. Pass only
VaultRefandtenant_id, never PAN-derived material. - Using a single KMS key for multiple tenants. If you operate a multi-tenant vault, derive per-tenant DEKs via the KMS
Deriveoperation (AWS KMS supports this via key context; HashiCorp Vault Transit supports it viaderived=true). - Treating the KMS as the CDE boundary. The KMS holds the KEK, not the PAN. The CDE is wherever the plaintext PAN lives, which is the vault process during the brief window between
unwrap_dekand AES decryption + the subsequent acquirer post.