Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Engyon: enprot
Document confidentiality and provenance for collaborative text. enprot embeds classification levels, content-addressed storage, and signed chain anchors inside any text-based file — without breaking the host language's syntax.
Built on the Ribose Standard for Engyon Protected Text (RSD 12001), enprot implements the document edge of a complete trust stack: standalone for individual use; integrated with Confium for distributed trust, threshold signing, and hardware key custody.
When to use enprot
| Need | Use enprot if… |
|---|---|
| Encrypt parts of a source file | You want WORD-level confidentiality, not whole-file |
| Multi-level classification in one doc | Same file carries PUBLIC + CONFIDENTIAL + SECRET segments |
| Tamper-evident edit history | Chain anchors sign every transform; verify without decrypting |
| Merge-friendly signed regions | CONFLICT blocks keep the file valid host-language source |
| PQ-ready signatures | ML-DSA, ML-KEM, composite Ed25519+ML-DSA ship today |
| Distributed signing authority | Confium integration (planned) for k-of-n threshold |
| Hardware key custody | Confium stores: TPM, HSM, PKCS#11, OpenPGP card, cloud KMS |
| Integrity without confidentiality | IMMUTABLE/MUTABLE blocks per RSD spec |
How enprot compares
| Feature | enprot | git-crypt | sops | age | sigstore |
|---|---|---|---|---|---|
| Encryption in source comments | yes | yes | partial (YAML/JSON) | no | no |
| Content-addressed storage | yes | no | no | no | yes (Rekor) |
| Signed chain anchors in-file | yes | no | no | no | partial (sidecar) |
| Merge-friendly regions | yes | no | no | no | no |
| Multi-level classification | yes | no | no | no | no |
| IMMUTABLE integrity blocks | yes | no | no | no | no |
| PQ-ready (ML-DSA, ML-KEM) | yes | no | no | no | partial |
| OpenPGP interop | yes (rnp-rs) | via GPG | no | no | no |
| Threshold signing | via Confium | no | no | no | no |
| Hardware key custody | via Confium | partial (GPG) | partial | no | yes (KMS) |
| Audit transparency log | via Confium | no | no | no | yes (Rekor) |
Buyer ladder
Phase 1 (now): Individual → enprot standalone (this repo)
Phase 2 (6-12 mo): Team / release eng → enprot + shared Confium daemon
Phase 3 (12-24 mo): Enterprise → + Confium stores (TPM/HSM) + attributes
Phase 4 (24+ mo): Cross-org → + Confium BLS threshold + transparency
- Human-editable markup: EPT directives sit inside host-language comments, so files stay valid C, Rust, Python, Markdown, HTML, or LaTeX.
- Content-addressed storage (CAS): segments can be sanitized out of the document and stored by their SHA3-256 hash. Same content always produces the same CAS file — deterministic, dedup-friendly.
- Authenticated encryption: AES-256-SIV (default), AES-256-GCM, or
AES-256-GCM-SIV. Password-derived keys via Argon2, Scrypt, or PBKDF2.
ML-KEM-based multi-recipient encryption via
--recipient. - Deterministic AEAD: optional
aes-256-gcm-det/aes-256-gcm-siv-detvariants derive the nonce from the plaintext so identical content always produces identical ciphertext — enabling CAS dedup on encrypted segments. - Chain anchors: signed
CHAINblocks provide tamper-evident file history. Multi-signer anchors require N parties to sign. - Merge-friendly: WORD-region merge driver emits
CONFLICTblocks that keep the file valid host-language source.enprot resolveclears them. CAS-referenced blocks deduplicate across branches. - Post-quantum ready: ML-DSA signatures, ML-KEM key encapsulation, and composite Ed25519+ML-DSA constructions ship today.
Installation
From crates.io
From source
Enprot requires Botan 3.x and
librnp. Install via brew install botan rnp
on macOS, your distro's package manager on Linux, or run the CI install
scripts which build both from source:
| Platform | Install script | Notes |
|---|---|---|
| macOS | ci/install.sh |
Homebrew (brew install botan rnp) is simpler |
| Linux | ci/install.sh |
Builds Botan + librnp from source |
| Windows | ci/install.ps1 |
MSVC only — builds the full C dep stack from source (bzip2, zlib, Botan, json-c, librnp) |
or build locally:
Shell completions
After installation, generate completions for your shell:
|
Quick start
EPT markup
An EPT file is a normal text file containing directives hidden inside
host-language comments. The default separators are // <( (left) and
)> (right), which work for C, C++, Rust, JavaScript, and other
//-comment languages.
A simple example (sample/test.ept):
hello, this is a test file
// <( BEGIN GEHEIM )>
Secret line 1
Secret line 2
// <( BEGIN Agent_007 )>
James Bond
// <( END Agent_007 )>
// <( END GEHEIM )>
// <( BEGIN Agent_007 )>
Super secret line 3
// <( END Agent_007 )>
Directive types:
| Directive | Meaning |
|---|---|
BEGIN <WORD> |
Opens a named segment. Segments can nest. |
END <WORD> |
Closes the matching segment. |
STORED <WORD> <hash> |
A CAS pointer — the segment's content lives in the CAS file named <hash>. |
ENCRYPTED <WORD> [hash] [pbkdf:… cipher:…] |
An encrypted segment. If a hash is present, the ciphertext is in CAS; otherwise inline DATA lines follow. |
DATA <base64> |
One or more base64-encoded ciphertext lines inside an ENCRYPTED block. |
CHAIN key:val … |
Signed chain anchor — tamper-evident history. |
INCLUDE <hash> |
Cross-file CAS reference (provenance manifests). |
CONFLICT <WORD> |
Merge-driver conflict marker (resolved by enprot resolve). |
IMMUTABLE <name> <hashalg>=<hash> |
Content-addressed integrity block (RSD spec). Verified by enprot verify. |
MUTABLE <name> |
Closes an IMMUTABLE block. |
MUTED <name> <hashalg>=<hash> |
Sanitized IMMUTABLE — content replaced by hash reference (in CAS). |
KEY <name> <hashalg>=<hash> |
Declares a key binding by content hash (RSD spec). |
UNKEY <name> |
Ends a KEY binding scope. |
CERT <name> <hashalg>=<hash> |
Declares a public-key cert binding by content hash. |
UNCERT <name> |
Ends a CERT binding scope. |
RSD spec aliases (input-only; writer emits canonical form):
| Spec vocabulary | Canonical enprot form |
|---|---|
CLASSIFY / UNCLASSIFY |
BEGIN / END |
CLASSIFIED |
ENCRYPTED |
SIGNED / SIGNATURE |
BEGIN / ENCRYPTED |
Host-language separators
Use -l/-r to override the separators for non-// languages, or use
the --lang preset:
--lang |
Left sep | Right sep |
|---|---|---|
raw |
<( |
)> |
c |
// <( |
)> |
python |
# <( |
)> |
html |
<!-- <( |
)> --> |
latex |
% <( |
)> |
Example: encrypt a Python file's segments with # <( comments:
Subcommands
encrypt
Encrypt WORD segments inline (ciphertext stays in the document):
If no -k is given, you are prompted interactively (with repeat).
Encrypt-specific options (run enprot encrypt --help for the full list):
| Option | Description |
|---|---|
--cipher <ALG> |
aes-256-siv (default), aes-256-gcm, aes-256-gcm-siv, aes-256-gcm-det, aes-256-gcm-siv-det |
--pbkdf <ALG> |
argon2 (default), scrypt, pbkdf2-sha256, pbkdf2-sha512, legacy |
--pbkdf-msec <MSEC> |
Time-budget for KDF parameter tuning (default 100) |
--pbkdf-salt-len <N> |
Salt length in bytes (default 16) |
--pbkdf-params <K=V,…> |
Override KDF parameters manually (testing) |
--pbkdf-salt <HEX> |
Fixed salt (testing) |
--pbkdf-disable-cache |
Disable PBKDF cache (slower, but no shared derivation state) |
--cipher-iv <HEX> |
Fixed IV (testing) |
--recipient <PUB.pem> |
ML-KEM multi-recipient encryption (repeatable) |
--inline |
Force inline DATA blocks instead of CAS-referenced |
decrypt
Decrypt WORD segments:
Multiple WORDs are comma-separated or repeated. Multiple -k flags
supply passwords for different WORDs.
For KEM-mode blocks (encrypted with --recipient):
store
Sanitize WORD segments to CAS — replace the segment content with a
STORED <WORD> <hash> pointer and write the original content to a file
named by its SHA3-256 hash:
After storing, the CAS directory contains the content:
$ ls cas
cea67c3ef34ff899793b557e9178c1b97bbcfe9722df2f6d35d2d0c91d2c1fe4
The same content always produces the same hash, so storing is idempotent — re-storing identical content is a no-op.
fetch
Restore WORD segments from CAS:
encrypt-store
Encrypt and store in one step. The ciphertext goes to CAS and is referenced by hash:
verify
Check file integrity without decrypting. Validates markup structure, CAS pointer resolution (file exists + hash matches), and extfield formatting:
Output is OK or FAIL per file. Exits non-zero on any failure —
useful in CI pipelines.
list
List all WORD segments in a file with their type and crypto metadata:
For multiple files, each is headed with == <path> ==.
passthrough
Parse and re-write a file without applying any transform. Useful for validating markup or measuring parse performance:
completions
Print a shell completion script:
|
inspect
Combined diagnostic: show structure + chain anchors + conflicts + capabilities in one pass (exit non-zero if conflicts remain):
keygen / sign / verify-sig
Detached signatures (Ed25519, ML-DSA, and composite constructions):
# Generate a keypair (PEM, one file each).
# Sign a file → produces FILE.sig (raw 64-byte Ed25519 signature).
# Verify → reads document.txt.sig by default.
Multi-signer bundles (repeat --key-file):
Override the signature path with --sig-file PATH (verify-sig) or
-o PATH (sign). Both commands read stdin when no FILE is given.
Chain anchors
Signed chain anchors provide tamper-evident file history:
# Produce an anchor after a transform.
# Verify anchors against a trusted pubkey.
# Print the current chain head hash (publish out-of-band).
# Verify against a published pin.
# List unresolved conflicts (exit non-zero if any).
Merge driver + conflict resolution
# Git merge-driver contract (%O=ancestor, %A=ours, %B=theirs, %P=path).
# Resolve conflicts (one mode for all, or per-WORD overrides).
Git filters
# stdin → stdout pipes for git filter/textconv integration.
Provenance + supply chain
# SLSA-style provenance manifest.
# Supply-chain manifest with Cargo.toml dep parsing.
Common options
These flags work with every subcommand (before or after the subcommand name):
| Flag | Description |
|---|---|
-v, --verbose |
Show parse/transform/write progress on stderr |
-q, --quiet |
Suppress non-essential output |
-k, --key <WORD=PASSWORD> |
Supply a password (repeatable; one pair per flag) |
-c, --casdir <DIR> |
CAS directory (default ./cas if it exists, else .) |
-l, --left-separator <SEP> |
Left EPT separator (default // <() |
-r, --right-separator <SEP> |
Right EPT separator (default )>) |
--lang <LANG> |
Preset separators: raw, c, python, html, latex |
--policy <POLICY> |
Crypto policy: default or nist |
--defaults <POLICY> |
Load a policy's defaults without enforcing it |
--fips |
Force FIPS-compliant algorithms (implies --policy=nist) |
--max-depth <N> |
Maximum nesting depth (default 100; 0 = infinite) |
-w, --word <WORD> |
WORD(s) to operate on (repeatable, also comma-separated) |
-o, --output <FILE> |
Output file for the previous input (repeatable) |
-p, --prefix <PREFIX> |
Prefix for output filenames (directory mode if ends with /) |
--output-dir <DIR> |
Write outputs into DIR by basename (conflicts with -p) |
--format <text|json> |
Output format for inspection subcommands |
--policy-file <PATH> |
Capability policy TOML file |
--anchor |
Append a signed chain anchor after transform |
--inline |
Force inline DATA blocks instead of CAS-referenced |
--pbkdf-disable-cache |
Disable PBKDF cache |
Deterministic encryption
By default, AES-GCM and AES-GCM-SIV use random nonces — encrypting the same plaintext twice produces different ciphertexts. This breaks CAS deduplication for encrypted segments.
The -det cipher variants derive the nonce from the plaintext via
HKDF + HMAC, making encryption fully deterministic:
| Cipher | Construction |
|---|---|
aes-256-gcm-det |
enc_key = HKDF(master, "enc"), iv = HMAC(iv_key, pt)[..12], then AES-256-GCM |
aes-256-gcm-siv-det |
Same HKDF/HMAC construction, then AES-256-GCM-SIV (misuse-resistant) |
Same (password, plaintext) always produces the same ciphertext, so
encrypt-store on identical segments deduplicates in CAS.
Usage:
Multi-file processing
Process wildcards in one invocation. Passwords are prompted once:
Output to a different file:
Output to a directory (basename preserved):
# or equivalently:
Plain prefix (prepended verbatim — legacy behavior):
# produces: build_src/main.ept, build_src/lib.ept, …
Working on source code
EPT directives inside comments don't break compilation. Enprot's own
source has an encrypted AUTHOR block in src/lib.rs:
// <( ENCRYPTED AUTHOR )>
// <( DATA X417HVMRRAs6Z1xGo5yY4TxUQ2tpAHEKQ1sg9+kfku5uUikK3y2tODtsUiGqfRGW )>
// <( DATA xUCGYFu02BCdqPM7uuX5UNvbfrLvKkj6gLYwg/cr42PJmr4o5xnw1qo= )>
// <( END AUTHOR )>
Decrypt it:
Crypto policies
Two built-in policies control which algorithms are permitted:
default |
nist |
|
|---|---|---|
| Default cipher | AES-256-SIV | AES-256-GCM |
| Default PBKDF | Argon2 | PBKDF2-SHA-512 |
| Allowed hashes | Any | SHA3-256, SHA3-512 |
| Min salt length | None | 16 bytes |
| Min iterations | None | 1000 |
| GCM IV width | Any | 96 bits |
--fips forces the nist policy. On Linux, it also auto-engages
when /proc/sys/crypto/fips_enabled reads 1.
See docs/fips.adoc for details on what a fully FIPS-validated build
would require.
Cross-compiling for OHOS (OpenHarmony)
enprot builds for aarch64-unknown-linux-ohos (HarmonyOS arm64). The cross-compile
setup is in ci/setup-ohos-ndk.sh (NDK download + sysroot symlink) and
ci/build-botan-ohos.sh (static Botan). CI runs the full pipeline in
.github/workflows/ohos.yml and verifies the binary in dockerharmony
(real OHOS userland via qemu binfmt).
Local reproduction:
The Rust target uses the unknown vendor field (aarch64-unknown-linux-ohos);
the NDK clang binary is named after the Rust triple, but the sysroot path
and --target= flag use the NDK triple (aarch64-linux-ohos, no vendor).
See docs/ohos-porting-guide.md for the full porting reference
(NDK architecture, two-sysroots problem, code signing, CI topology).
Compatibility
Documents encrypted by enprot <=0.3.1 still decrypt. The wire format of
the pbkdf: and cipher: extended fields is unchanged:
// <( ENCRYPTED <keyword> [cas-hash] pbkdf:$<id>$<k=v,k=v>$<base64-salt> cipher:$<alg>$iv=<base64-iv> )>
When the ciphertext is in CAS rather than inline, the CAS hash replaces
the inner DATA lines and the cipher: field carries the IV.
The legacy PBKDF (plain SHA3-512 truncation) is retained for
decrypting old blobs; selecting it for new encryption prints a warning.
See docs/migration-0.3-to-0.4.md for the full migration guide
(subcommand CLI, CAS-referenced default, CONFLICT directive, chain
anchors, feature-gated library).
Configuration
enprot init writes .enprot.toml. Layered resolution:
- Built-in defaults
~/.config/enprot/config.toml(user global).enprot.toml(project local, walked up from cwd)ENPROPT_*environment variables- CLI flags (highest precedence)
= "cas"
= "c"
[]
= "aes-256-gcm-siv-det"
= "argon2"
[]
# signer = "confium://session-id"
# auto_anchor = true
Library usage
Enprot can be used as a Rust library without the CLI:
[]
= { = "0.4", = false }
The default-features = false build excludes clap and clap_complete.
All library modules (crypto, parsing, capability model, chain anchors,
Merkle trees, merge driver, provenance, SCM) are available.
Development
See CONTRIBUTING.md for the full guide. Quick reference:
&&
Architecture docs: CLAUDE.md. Wire format specs: docs/schemas/.
Upgrade history: TODO.upgrade/, TODO.audit/, TODO.issues/,
TODO.finalize/, TODO.roadmap/.
License
BSD-2-Clause. See LICENSE file.