Data plane

Field security as a contract.

Mark a field encrypted, hashed, or blind-indexed. Vadyl applies the right cryptography after validation, before plan building. Reads decrypt only after access evaluation — masked fields never trigger decryption. Native acceleration kicks in where supported. AEAD AAD binds every ciphertext to its tenant, entity, field, and key version. Cross-context paste yields authentication failure — by design.

Six orthogonal dimensions

Composable, not a flat enum.

FieldSecurityDescriptor declares six independent axes. Encrypted, Hashed, BlindIndexed are specific compositions. The flat enum trap — a single security mode that can't combine — is forbidden by construction.

StorageProtectionKind

None / Aead / Hash / Hmac. The actual protection on the column. Aead = randomized envelope. Hmac = the field IS the blind index.

SearchabilityKind

Full / EqualityViaBlindIndex / None. Determines what filters the read planner allows. None blocks every comparison; EqualityViaBlindIndex permits =, !=, IN only.

VerificationKind

None / CandidateVerify. For password / token fields. Timing-safe verification through IFieldVerificationService — never bare comparison.

HashAlgorithmKind

Pbkdf2Sha256 / Bcrypt / Argon2id. For verifiable hashes. Self-describing format — the algorithm + parameters travel with the hash.

NativeAccelerationKind

None / AlwaysEncrypted / PgCrypto / MySqlAes. Native column-level encryption when the provider declares the capability and the operator opts in. Defense in depth, not the correctness layer.

KeyRingId

Which IKeyRing key set protects this field. Multiple rings let you rotate independently — customer-PII on a different schedule than internal-tokens.

The envelope

Versioned wire format. AAD-bound to context.

Every AEAD-encrypted field rides in a versioned envelope. The AAD binds the ciphertext to its tenant, entity, field name, and key version. Cross-context copy-paste cannot decrypt — GCM authentication fails by construction.

Wire format

[version:1][keyVer:4][algo:1][flags:1][nonce:12][tag:16][ciphertext:N]

Versioned. Forward-compatible. The reader knows which key to fetch from the ring.

AAD binding

tenantId ‖ 0x00 ‖ entityName ‖ 0x00 ‖ fieldName ‖ 0x00 ‖ keyVersion

A ciphertext from tenant A in entity Customer can never be decrypted as if it lives in tenant B's Order — same key, different AAD, GCM fails.

The pipeline

Encryption after validation. Decryption after access.

Write path

Validate → IFieldSecurityInterceptor.protectFieldsAsync (encrypt / hash / hmac) → MutationPlan → provider. Field-level write permissions evaluate first; protected fields without CanWrite never reach the interceptor.

Read path

Provider returns rows → AccessEngine evaluates field masks → FieldProjectionBuilder applies Omit / Null / Redacted → IFieldSecurityInterceptor.unprotectFieldsAsync runs only on fields that survived masking. Masked fields never trigger decryption.

Re-key path

DataProtectionBackfill runs as an online checkpoint-resumable backfill — the same plumbing that handles schema transitions. Old key marked DecryptOnly. Reads work. Writes use the new key. Old key retires once the backfill drains.

6
Orthogonal dimensions

Composable, not flat

AEAD
App-level correctness

Native crypto = defense in depth

AAD
Bound to context

Tenant, entity, field, key version

Online
Re-key

Resumable, no downtime

Encryption that composes with the rest of your model.

Mark a field encrypted. Vadyl handles the keys, the AAD, the masking, the searchability rules, the re-key path. No shadow encryption layer. No double bookkeeping.