Post-quantum archive encryption, threshold recovery, and detached authenticity for long-lived files.
Deep dive: see docs/WHITEPAPER.md.
Documentation map: see docs/README.md.
This README is the product-facing overview: it complements the whitepaper and is meant for onboarding, first impression, and understanding the workflow quickly. Byte-level formats, detailed policy semantics, and the long-term archival roadmap live under docs/.
Quantum Vault currently ships one successor lifecycle family:
QVqcont-7shards- archive-state descriptors
- cohort bindings
QV-Lifecycle-Bundlev1
Archive approval is over canonical archive-state descriptor bytes. Lifecycle bundles stay mutable so detached signatures, signer material, timestamp proofs, transition records, and source-evidence objects can accumulate over time without invalidating the signed archive-state bytes.
- What Quantum Vault Is For | Features | Workflow | Authenticity | Security | Docs | Development | License
Quantum Vault is a browser-based tool for protecting long-lived files with post-quantum encryption, threshold recovery, and detached authenticity proofs. A normal encrypted file solves confidentiality at one point in time, but long-lived archives often also need durable recovery, signer-verifiable provenance, mutable evidence that can be attached later, and an explicit rule for when restore is allowed. Quantum Vault is designed for archives that need to survive both post-quantum transition risk and ordinary operational failure.
Long-lived archives also force three questions that should not be collapsed into one verdict. Confidentiality asks whether captured ciphertext can still resist later decryption; NIST IR 8547 treats this harvest-now-decrypt-later risk as the urgent migration driver for key-establishment schemes. Authenticity asks whether a signer key approved a specific archive state; that is why Quantum Vault keeps detached signatures and signer pinning separate from AEAD integrity. Time evidence asks whether a signed artifact can be shown to have existed before a claimed time boundary; the current implementation can link OpenTimestamps proofs to detached signature bytes, but RFC 4998-style renewable evidence remains future work.
| If you need to... | Quantum Vault does this by... |
|---|---|
| Keep archive contents confidential even if storage is copied | Encrypting locally in the browser with ML-KEM-1024, KMAC256, and AES-256-GCM |
| Avoid a single backup location becoming a single point of failure | Splitting one archive into threshold QVqcont-7 shards |
| Show that a specific signer key approved one archive state | Verifying detached .qsig or Stellar .sig proofs over the canonical archive-state descriptor |
| Add signer keys or timestamp evidence later without invalidating archive approval | Storing mutable authenticity artifacts in QV-Lifecycle-Bundle v1 instead of in the signed archive-state bytes |
| Block restore unless authenticity requirements are met | Evaluating archive policy during restore (integrity-only, any-signature, strong-pq-signature) |
Implemented now:
- one shipped successor lifecycle family:
QVqcont-7shards,quantum-vault-archive-state-descriptor/v1, cohort bindings, andQV-Lifecycle-Bundlev1 - Split emits successor
.qcontshards plus*.archive-state.jsonand*.lifecycle-bundle.json; Pro additionally exports*.cohort-binding.json, although successor shards always embed cohort-binding bytes even when Lite does not export a standalone cohort-binding file. - The shipped browser Attach workflow accepts successor shards or an existing lifecycle bundle plus optional target artifacts, detached signatures (
.qsig,.sig),.pqpksigner pins, and.otsproofs for archive-approval, maintenance, and source-evidence channels. - Maintenance and source-evidence signatures can be imported through Attach and are reported at restore when present, but they remain separate evidence channels and never satisfy archive restore policy.
- Restore evaluates archive approval from canonical archive-state bytes and fails closed when ambiguity remains in
archiveId,stateId,cohortId, or embedded lifecycle-bundle digest unless the operator makes an explicit choice; explicit operator selection is a warned override, not an automatic winner selection. - Same-state resharing is implemented for successor lifecycle archives and preserves archive-state bytes while emitting a new cohort and required transition record.
Deferred roadmap:
- RFC 4998-style renewable evidence
- state-changing continuity records across future rewrap or reencryption
- governance or trust-root objects
- ML-KEM-1024 (FIPS 203) for post-quantum key encapsulation
- KMAC256 (SP 800-185) for key derivation
- AES-256-GCM (SP 800-38D) for payload encryption and AEAD integrity
- SHA3-512 (FIPS 202) for fixity, binding, and lifecycle digests
- Shamir Secret Sharing for private-key sharding
- Reed-Solomon erasure coding for ciphertext fragment recovery
- Generate a post-quantum ML-KEM key pair directly in the browser.
- Encrypt one or more files into a
.qenccontainer for long-lived confidentiality. - Decrypt Quantum Vault
.qenccontainers back into their original payload. - Split an archive and its ML-KEM private key into threshold successor
.qcontshards, exporting a signable archive-state descriptor and lifecycle bundle for later approval and maintenance; Pro additionally exports a standalone cohort-binding artifact for operator workflows. - Attach archive-approval, maintenance, or source-evidence detached signatures, signer pin material, and timestamp evidence into a lifecycle bundle carried by successor shards or supplied explicitly.
- Restore from successor shards plus optional authenticity material, with recovery gated by archive policy.
- Verify detached signatures while keeping integrity, signer pinning, maintenance evidence, source evidence, and policy satisfaction as separate outcomes.
- Keep cryptographic operations local to the client browser during normal operation.
- Lite Create shows non-blocking custody guidance (independent shard storage, retaining archive-state and lifecycle JSON, optional ML-KEM key backup) and a short export checklist after each successful create.
| Stage | Main inputs | Main outputs | What this stage adds |
|---|---|---|---|
| Generate | Browser entropy | privateKey.qkey, publicKey.qkey |
ML-KEM key pair generated client-side |
| Encrypt | File(s), publicKey.qkey |
.qenc |
Post-quantum confidentiality and AEAD integrity |
| Split | .qenc, privateKey.qkey |
Lite: .qcont, *.archive-state.json, *.lifecycle-bundle.json; Pro also: *.cohort-binding.json |
Threshold recovery; successor shards always embed archive-state, cohort-binding, and lifecycle-bundle bytes even when Lite does not export cohort-binding as a standalone file |
| Attach | Successor shards or lifecycle bundle, optional channel target artifact, .qsig/.sig, optional .pqpk, optional .ots |
Updated lifecycle bundle, optional rewritten .qcont shards |
Imports archive-approval, maintenance, or source-evidence signatures through the selected channel; archive-state and cohort-binding bytes stay unchanged |
| Restore | .qcont, optional archive-state descriptor, lifecycle bundle, signatures, pins, timestamps |
Recovered .qenc, recovered privateKey.qkey |
Policy-gated archive reconstruction with explicit selection whenever ambiguity exists |
| Decrypt | .qenc, privateKey.qkey |
Original file(s) | Payload recovery and file-hash confirmation |
flowchart TB
subgraph CREATE["Create archive"]
G["Generate key pair<br/>ML-KEM-1024 KeyGen"]:::crypto
PK["publicKey.qkey"]:::artifact
SK["privateKey.qkey"]:::artifact
U["User file(s)"]:::input
E["Encrypt<br/>ML-KEM-1024 Encaps → KMAC256 → AES-256-GCM"]:::crypto
Q[".qenc"]:::artifact
S["Split<br/>Shamir(privateKey) + Reed-Solomon(ciphertext)"]:::split
QC[".qcont shards<br/>(embedded archive-state + cohort binding + lifecycle bundle)"]:::artifact
AS["Lite + Pro<br/>*.archive-state.json"]:::artifact
CB["Pro export<br/>*.cohort-binding.json"]:::artifact
LB["Lite + Pro<br/>*.lifecycle-bundle.json"]:::artifact
G --> PK
G --> SK
U --> E
PK --> E
E --> Q
Q --> S
SK --> S
S --> QC
S --> AS
S -. Pro/operator export .-> CB
S --> LB
end
subgraph AUTH["Add authenticity"]
SG["Sign externally"]:::auth
SIG[".qsig / .sig"]:::artifact
AUX["optional transition-record / source-evidence target"]:::optional
OPT["optional .pqpk / .ots"]:::optional
AT["Attach"]:::auth
EM["Updated lifecycle bundle<br/>or rewritten shard set"]:::artifact
AS --> SG
AUX -. maintenance/source target .-> SG
SG --> SIG
QC -. embedded lifecycle bundle .-> AT
LB --> AT
AS -. optional consistency input .-> AT
AUX -. maintenance/source target .-> AT
SIG --> AT
OPT --> AT
AT --> EM
end
subgraph RECOVER["Recover archive"]
SEL["Resolve successor restore path<br/>(explicit choice only when ambiguous)"]:::restore
R["Restore<br/>(policy-gated reconstruction)"]:::restore
OUT["Recovered .qenc + privateKey.qkey"]:::artifact
D["Decrypt<br/>ML-KEM-1024 Decaps → KMAC256 → AES-256-GCM"]:::restore
F["Original file(s)"]:::input
QC --> SEL
AS -. optional state narrowing .-> SEL
LB -. optional explicit bundle choice .-> SEL
SEL --> R
SIG -. optional .-> R
OPT -. optional .-> R
R --> OUT
OUT --> D
D --> F
end
classDef input fill:#f5f5f5,stroke:#666,stroke-width:1px,color:#111;
classDef crypto fill:#e8f1ff,stroke:#3d5a80,stroke-width:1.5px,color:#111;
classDef split fill:#fff1d6,stroke:#a06b00,stroke-width:1.5px,color:#111;
classDef auth fill:#f3e8ff,stroke:#7c3aed,stroke-width:1.5px,color:#111;
classDef restore fill:#e8f7e8,stroke:#2e7d32,stroke-width:1.5px,color:#111;
classDef artifact fill:#ffffff,stroke:#444,stroke-width:1px,color:#111;
classDef optional fill:#fafafa,stroke:#999,stroke-width:1px,color:#555;
The shipped path is successor-only. Attach can start from successor shards carrying an embedded lifecycle bundle or from an explicitly provided lifecycle bundle, and restore inserts an explicit selection step whenever multiple archive, state, cohort, or lifecycle-bundle candidates exist.
| Stage | User action | Required inputs | Primary outputs | Important behavior |
|---|---|---|---|---|
| 1. Generate | Create a key pair in-browser | None | privateKey.qkey, publicKey.qkey |
Secrets stay client-side only |
| 2. Encrypt | Encrypt one file or a local file bundle | File(s), publicKey.qkey |
.qenc |
Uses ML-KEM-1024 + KMAC256 + AES-256-GCM |
| 3. Split | Convert one archive into threshold shards | .qenc, matching privateKey.qkey |
Lite: .qcont, *.archive-state.json, *.lifecycle-bundle.json; Pro also: *.cohort-binding.json |
Emits QVqcont-7 shards and successor lifecycle objects |
| 4. Sign | Sign the archive-state descriptor or a maintenance/source-evidence target in an external signer app | *.archive-state.json, transition-record.json, or source-evidence.json |
.qsig or .sig |
Archive approval signs canonical archive-state bytes; other channels sign their declared lifecycle target bytes |
| 5. Attach | Merge archive-approval, maintenance, or source-evidence signatures, .pqpk pins, and .ots proofs into the mutable bundle |
Successor shards or lifecycle bundle, optional channel target artifact, detached artifacts | Updated *.lifecycle-bundle.json, optional rewritten shards |
The selected channel controls target validation and policy role |
| 6. Restore | Reconstruct from shards with optional authenticity inputs | .qcont, optional archive-state descriptor, lifecycle bundle, signatures, pins, timestamps |
Recovered .qenc, recovered privateKey.qkey |
Recovery is blocked unless policy is satisfied; ambiguity requires explicit operator choice |
| 7. Decrypt | Decrypt the recovered archive | .qenc, recovered privateKey.qkey |
Original file(s) | The UI confirms privateMeta.fileHash before export |
| Artifact | Produced by | Purpose | Notes |
|---|---|---|---|
publicKey.qkey |
Generate | ML-KEM public key | Used to encrypt |
privateKey.qkey |
Generate | ML-KEM private key | Used to decrypt |
.qenc |
Encrypt | Encrypted container | Carries public metadata, key commitment, ciphertext |
.qcont |
Split / Reshare | Threshold shard | QVqcont-7 shards embed archive-state, cohort binding, and lifecycle bundle |
*.archive-state.json |
Split / Attach | Canonical signable archive-state descriptor | Immutable archive-approval payload |
*.cohort-binding.json |
Split / Reshare | State-bound cohort description | Captures sharding commitments and the cohortId derivation input |
*.lifecycle-bundle.json |
Split / Attach / Reshare | Mutable lifecycle bundle | Carries policy, public keys, archive-approval signatures, maintenance signatures, source-evidence signatures, timestamps, transitions, and source evidence |
.qsig |
Quantum Signer | Detached PQ signature | Used for archive approval or other declared lifecycle targets |
.sig |
Stellar WebSigner | Detached Ed25519 signature proof | Same target-family rule as .qsig |
.pqpk |
Quantum Signer | Detached PQ public key | Used for bundled pinning or user pinning |
.ots |
External timestamp tool | OpenTimestamps evidence | Linked to detached signature bytes, not to lifecycle-bundle bytes |
Quantum Vault keeps the following states separate:
- integrity verified
- archive approval signature verified
- signer identity pinned
- archive policy satisfied
- maintenance signature verified
- source-evidence signature verified
- OTS evidence linked
Archive approval, maintenance, and source evidence are different channels:
- archive-approval signatures target canonical archive-state descriptor bytes and are the only signatures that satisfy archive policy
- maintenance signatures target transition-record bytes and are reported separately
- source-evidence signatures target source-evidence bytes and are reported separately
- timestamp evidence is supplementary and does not satisfy archive policy by itself
Current browser Attach target rules:
- archive-approval attach validates detached signatures over the selected canonical archive-state descriptor; the target reference is
state:{stateId} - maintenance attach requires the selected lifecycle bundle to already contain the target transition record and requires the matching canonical
transition-record.json; detached signatures targettransition:sha3-512:{transitionRecordDigest} - source-evidence attach requires canonical
source-evidence.json, adds that object to the lifecycle bundle if needed, and validates detached signatures againstsource-evidence:sha3-512:{sourceEvidenceDigest} .pqpkfiles provide signer public-key material for bundled or user-supplied pinning; pinning remains separate from policy satisfaction.otsfiles are resolved against known detached signature bytes bySHA-256(signatureBytes)and fail closed if the proof matches zero or multiple signatures
Current trust boundary for detached PQ signatures:
- a
.qsigthat verifies only with the public key embedded inside the.qsigitself is treated as cryptographic self-verification, not as externally anchored signer identity - such a self-verified
.qsigcan still be reported as internally consistent proof material, but it does not satisfy trust or archive policy unless bundled or user-supplied signer material also verifies
| Policy level | Minimum requirement | Unsigned restore allowed | Ed25519-only signatures sufficient |
|---|---|---|---|
integrity-only |
No detached archive-approval signature required | Yes | Yes, but not required |
any-signature |
minValidSignatures valid archive-approval signatures |
No | Yes |
strong-pq-signature |
minValidSignatures valid archive-approval signatures and at least one valid strong-PQ archive-approval signature |
No | No |
Current shipped defaults:
- Lite mode defaults to
integrity-only - Pro mode defaults to
strong-pq-signature
Detailed current rules live in:
docs/format-spec.mdfor container formats, lifecycle objects, verifier flow, and fail-closed behaviordocs/trust-and-policy.mdfor signature meaning, proof counting, pinning, archive policy, and restore authorization semantics
- All cryptographic operations are intended to happen in the client browser; no private archive material should need to leave the user environment.
- JavaScript cryptography cannot provide strong side-channel guarantees against hostile local environments.
- ML-KEM gives confidentiality, not sender authentication; detached signatures and policy evaluation provide provenance at the archive layer.
- Shamir sharing protects confidentiality below threshold, but users still need independent and reliable shard custody.
- OpenTimestamps evidence is currently evidence-only and does not replace signature validation or policy satisfaction.
- Attach fails closed if an
.otsproof does not match exactly one known detached signature; restore reports bad or unresolved OTS linkage on the timestamp-evidence channel and can still complete when archive policy is otherwise satisfied. - Current OTS linkage is real, but
appears complete/completeProofare heuristic reporting labels rather than a claim that a full Bitcoin attestation chain was independently validated. - The current heuristic checks filename-style keywords first:
initial,pending, orincompleteimply incomplete;complete,completed,confirmed, orupgradedimply complete; when no keyword matches, the fallback is proof size>= 1024bytes. - Quantum Vault is intentionally not a trust-in-a-service system: current verification semantics do not require a QV-operated timestamp server, an "official operator", or a permanent corporate trust root.
- Future evidence renewal is expected to use portable evidence chains and potentially multiple witness regimes; RFC 4998 is relevant as a renewal benchmark, while current OpenTimestamps support is only one distributed witness regime.
- Inspired by diceslice and tidecoin.
| Topic | Source |
|---|---|
| ML-KEM-1024 | FIPS 203 |
| ML-DSA | FIPS 204 |
| SLH-DSA | FIPS 205 |
SHA-3 (SHA3-256, SHA3-512) |
FIPS 202 |
| KMAC256 | SP 800-185 |
| AES-256-GCM | SP 800-38D |
| AEAD interface discipline | RFC 5116 |
| PQ migration / HNDL framing | NIST IR 8547 (IPD) |
| KEM usage and algorithm-agility context | SP 800-227 |
| Ed25519 | RFC 8032 |
| Stellar address encoding | SEP-0023 |
| OpenTimestamps project | opentimestamps.org |
| If you want to read about... | Go to |
|---|---|
| System rationale, design depth, and the higher-level architecture | docs/WHITEPAPER.md |
| Current artifact formats, canonicalization, verifier flow, and fail-closed behavior | docs/format-spec.md |
| Signature meaning, proof counting, pinning, and archive policy semantics | docs/trust-and-policy.md |
| Threat model, assumptions, trust boundaries, and security invariants | docs/security-model.md |
| Long-horizon archival direction, evidence renewal, and archive classes | docs/long-term-archive.md |
| Shared terminology and status vocabulary | docs/glossary.md |
| Documentation roles, ownership, and source-of-truth map | docs/README.md |
npm install
npm run devAdditional commands:
npm run build
npm run selftestCI runs on pull requests targeting main and merge-queue merge_group checks.
GitHub Pages deployment is handled by GitHub Actions (.github/workflows/pages.yml) on pushes to main.
The Pages workflow also supports manual dispatch, but the jobs are hard-blocked unless the selected ref is main.
For a local Pages-equivalent build, use:
BASE_PATH=/quantum-vault/ npm run buildContributor orientation:
src/core/crypto/contains the format, crypto, shard, lifecycle, and verification logicsrc/core/features/contains Lite/Pro workflow wiring and UI flowssrc/app/contains browser/runtime adapters and restore input helpersscripts/contains the dev server, build script, and headless self-test entrypoint
This project is distributed under the terms of the GNU Affero General Public License v3.0. See the LICENSE file for the full text.
Browser encryption and decryption tool libraries (see their version in the package.json):
- SHA3-512 for hashing and KMAC256 for KDF noble-hashes;
- ML-KEM-1024 for post-quantum key encapsulation used in combination with AES-256-GCM for symmetric file encryption noble-post-quantum;
- Shamir's secret sharing algorithm for splitting shamir-secret-sharing;
- Reed-Solomon erasure codes for splitting based on ErasureCodes.
The application incorporates the following dependencies that are released under the permissive MIT License and Apache License 2.0.
| Library | Copyright holder | Upstream repository |
|---|---|---|
| shamir-secret-sharing | Privy | https://github.com/privy-io/shamir-secret-sharing |
| noble-post-quantum | Paul Miller | https://github.com/paulmillr/noble-post-quantum |
| noble-hashes | Paul Miller | https://github.com/paulmillr/noble-hashes |