Hash your files. Anchor to Bitcoin. Prove any single file later — without revealing the rest.
simple-ots builds a SHA-256 Merkle tree over a set of files and stamps the root to Bitcoin via OpenTimestamps. For each file it generates one leaf per (path variant, DID) combination, so you can later prove a specific file to a third party with a short Merkle proof — without exposing anything else in the tree.
No ZKP. No external indexing. Just SHA-256 and a Merkle tree.
# Requires Go 1.22+
go install github.com/didvc/simple-ots@latestStamping requires the OpenTimestamps client:
pip install opentimestamps-clientTo build from source:
git clone https://github.com/didvc/simple-ots
cd simple-ots
./install.sh --local# Hash all Git-tracked files and stamp to Bitcoin
git ls-files | simple-ots
# Walk the current directory (no pipe needed)
simple-ots
# Offline — skip OTS
git ls-files | simple-ots --no-otsOutput lands in .simple-ots/results/YYYYMMDD_HHMMSS/:
| File | Contents |
|---|---|
manifest.jsonl |
One JSON line per leaf (file × path-variant × DID) |
root.hash |
Merkle root, hex SHA-256 |
root.hash.ots |
OpenTimestamps receipt |
Each file gets multiple leaf hashes — one per (path_variant, DID) pair. To prove a file later, hand the recipient that leaf's JSON plus its Merkle path (roughly 16 hashes). They verify up to the root, then run ots verify. Everything else in the tree stays invisible.
Put a config in .simple-ots/config.toml:
dids = ["did:web:example.com"]
path_variants = ["null", "filename", "relative"]
include_no_did = trueLeaves per file = len(path_variants) × (len(dids) + 1). The example above gives 6 per file.
See Configuration and Config Examples for more.
After a Bitcoin block confirms (~2 hours):
ots upgrade .simple-ots/results/TIMESTAMP/root.hash.ots
ots verify .simple-ots/results/TIMESTAMP/root.hash.ots| Flag | Default | Description |
|---|---|---|
--no-ots |
false |
Skip OpenTimestamps stamping |
--config PATH |
.simple-ots/config.toml |
Config file |
--verbose |
false |
Print each file as it's hashed |
--version |
Print version and exit | |
-h, --help |
Show usage |
Set OTS_CMD to override the ots invocation. Useful with pyenv:
export OTS_CMD="env PYENV_VERSION=3.9.9 ots"Paths are sorted alphabetically. For each file: compute content_sha256 (raw bytes) and read mtime. For each (path_variant, DID) combo, build a JSON object with four keys (content_sha256, datetime, did, path), serialize with sorted keys, and SHA-256 the result. All leaf hashes go into an in-memory Merkle tree. The root is 32 bytes regardless of how many files are in the tree.
man simple-otsThe man page is installed automatically by install.sh --local if a writable man directory is found.
See CONTRIBUTING.md.
Use the Cite this repository button on the GitHub repo, or see CITATION.cff.
- c2pa — C2PA examined from the standpoint of artistic authorship