Configuration
Configuration is read from .simple-ots/config.toml in the current directory (or from --config PATH).
The file is TOML format. All fields are optional — if the file doesn't exist, defaults are used.
Fields
dids
Type: []string
Default: [] (empty)
A list of DID strings to bind to each file's leaf hashes. Each DID produces a separate leaf per file per path variant. This is the core mechanism for selective disclosure of identity: you can reveal a file proof tied to one DID without disclosing that the same file is also tied to another identity.
dids = ["did:web:alice.example.com", "did:web:bob.example.com"]DIDs are arbitrary strings — any string that is unique and meaningful to you works. The did:web:, did:key:, and did:ion: methods are common choices, but there is no validation beyond being a non-empty string.
path_variants
Type: []string
Default: ["null", "filename", "relative"]
Valid values: "null", "filename", "relative"
Controls which representations of the file path are included in each leaf. Each variant produces a separate leaf, enabling selective disclosure of path information.
| Value | path field in leaf | Use case |
|---|---|---|
"null" | null | Prove file content and DID without revealing its location |
"filename" | "main.go" | Reveal the filename but not the directory structure |
"relative" | "src/main.go" | Reveal the full relative path from the project root |
path_variants = ["null", "filename", "relative"]include_no_did
Type: bool
Default: true
When true, an additional leaf is generated for each file with did = null, regardless of the dids list. This enables proving that a file existed without revealing any identity.
include_no_did = trueSetting this to false requires at least one DID in dids; otherwise no leaves would be generated for any file.
Leaf count formula
leaves_per_file = len(path_variants) × (len(dids) + (include_no_did ? 1 : 0))
total_leaves = files × leaves_per_fileExample: 500 files, 2 DIDs, 3 path variants, include_no_did = true:
leaves_per_file = 3 × (2 + 1) = 9
total_leaves = 500 × 9 = 45004500 SHA-256 hashes = ~144 KB of leaf data. Merkle tree depth ≈ 13 levels. OTS anchor = 1 root hash (32 bytes). Negligible overhead.
Canonical JSON structure
The leaf hash is SHA-256(canonical_JSON) where canonical JSON is produced by marshalling a map[string]interface{} with Go's encoding/json, which sorts keys lexicographically. The resulting JSON always has this shape:
{
"content_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"datetime": "2026-06-26T12:00:00Z",
"did": "did:web:example.com",
"path": "src/main.go"
}content_sha256— hex SHA-256 of raw file bytesdatetime— file mtime in RFC 3339 UTC (time.RFC3339, second precision)did— DID string or JSONnullpath— path string or JSONnull
Keys are always in alphabetical order (content_sha256, datetime, did, path). This makes the hash fully reproducible on any platform with any JSON library, as long as null values are included (not omitted).
Config file location
| Situation | Config loaded from |
|---|---|
| Default | <cwd>/.simple-ots/config.toml |
--config PATH | Exact path given |
| File missing | Built-in defaults (no error) |
Full example
# .simple-ots/config.toml
# Bind hashes to these identities
dids = [
"did:web:alice.example.com",
"did:web:bob.example.com",
]
# Path representations to generate per file
path_variants = ["null", "filename", "relative"]
# Always include a leaf with no DID (for anonymous proofs)
include_no_did = trueSee Config Examples for more patterns.