- Rust 85.6%
- TypeScript 13.3%
- Shell 0.4%
- Dockerfile 0.4%
- JavaScript 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| .slim | ||
| configs | ||
| crates | ||
| docs | ||
| examples | ||
| scripts | ||
| sdk | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| codemap.md | ||
| CONTRIBUTING.md | ||
| docker-compose.build.yaml | ||
| docker-compose.yaml | ||
| docker-entrypoint-gateway.sh | ||
| Dockerfile | ||
| LICENSE | ||
| mkdocs.yml | ||
| README.md | ||
| requirements.txt | ||
| SECURITY.md | ||
| TESTING.md | ||
| witness.webp | ||
🙌 Witness
A content-private, accountless quorum timestamper.
Witness lets a federation of independent operators co-sign that a hash existed at a particular time. Clients submit only SHA-256 hashes—never content—and receive a threshold signature from a quorum of witnesses; no account, login, or wallet is required. Batches can be cross-anchored by peer federations and committed to external systems (Internet Archive, Trillian, DNS, Ethereum) for additional, independent durability.
Features
- Durable & Free: Submission returns immediately; leased workers retry quorum signing without gas fees
- Threshold Signatures: Requires multiple independent witnesses to collude to forge
- Signature Scheme: Ed25519 multisignatures (fast, one signature per participating witness)
- Federation: Independent networks cross-anchor for additional security
- External Anchoring: Batch merkle roots to Internet Archive, Trillian, DNS, or Ethereum
- Light Clients: Merkle proofs for verification without full history
- Privacy: Only hashes are submitted, not content
- Admission Controls: Built-in per-IP and global limits, plus operator-controlled proxy/network admission
Project Documents
- Security Policy: vulnerability reporting, production baseline, and known limitations.
- Threat Model: security goals, assumptions, non-goals, actors, and current gaps.
- Operator documentation: gateway deployment, TLS/proxy setup, monitoring, backups, and upgrades.
- Release Packaging: release artifacts, checksums, container tags, and pre-tag checklist.
- Testing Guide: local, unit, integration, and manual test workflows.
- Contributing: development checks and security-sensitive change guidance.
- Changelog: release notes and compatibility changes.
Documentation site
The documentation site is built with Material for MkDocs. Install its pinned Python dependency, then serve or build the site from the repository root:
python -m pip install -r requirements.txt
mkdocs serve
mkdocs build --strict
Start with the documentation home or the quick start.
Quick Start
Docker (Recommended)
# Start the network (gateway + 3 witnesses)
docker compose up --build
# Reserve an attestation job for the SHA-256 hash of "hello"
curl -X POST http://localhost:8080/v1/attestations \
-H "Content-Type: application/json" \
-d '{"hash":"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"}'
Poll GET /v1/attestations/:hash until the job is confirmed; the response
then contains signed_attestation.
From Source
# Prerequisites: Rust 1.70+, SQLite
# Build
cargo build --release
# Set up example network (generates keys + config)
./examples/setup.sh
# Start network
./examples/start.sh
# Create an attestation job for a file
cargo run -p witness-cli -- attest --file README.md
Architecture
Client → Gateway → durable job in SQLite
↓ leased retry worker
Witnesses (threshold sign) → verified result
↓
Batch Manager → External Anchors
| Component | Description |
|---|---|
| witness-core | Types, Ed25519 multisignature verification, merkle trees |
| witness-node | Witness server that signs attestations |
| witness-gateway | Client API, threshold signing, batching, storage |
| witness-cli | Command-line tool for timestamping and verification |
CLI Usage
# Attest a file
witness attest --file document.pdf
# Attest a hash
witness attest --hash abc123...
# Poll job status
witness status <hash>
# Verify an attestation
witness verify attestation.json
# Verify a full proof online (explicit trust choice)
witness verify-proof --hash <hash> --online
# View network config
witness config
API Reference
Endpoints
| Method | Path | Description |
|---|---|---|
POST |
/v1/attestations |
Atomically reserve or retrieve an attestation job |
GET |
/v1/attestations/:hash |
Read a stable job snapshot |
POST |
/v1/verify |
Verify a signed attestation |
GET |
/v1/proof/:hash |
Get merkle inclusion proof and exact batch tree_size (light client) |
GET |
/v1/anchors/:hash |
Get external anchor proofs |
GET |
/v1/config |
Get informational network configuration summary |
GET |
/v1/network |
Get secret-free verification configuration |
GET |
/ws/events |
WebSocket for real-time attestation events |
GET |
/health |
Health check |
GET |
/metrics |
Prometheus metrics |
GET |
/admin |
Admin dashboard (if enabled and authenticated) |
Attestation Job Request
curl -X POST http://localhost:8080/v1/attestations \
-H "Content-Type: application/json" \
-d '{"hash":"a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"}'
New and nonconfirmed jobs return 202 Accepted. A duplicate already-confirmed
job returns 200 OK. Poll GET /v1/attestations/:hash until status is
confirmed. pending and retryable responses contain the immutable tuple but
never an unsigned SignedAttestation; failed is terminal. A confirmed response
includes signed_attestation.
Pending response
{
"attestation": {
"hash": "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",
"timestamp": 1699454445,
"network_id": "example-network",
"sequence": 42
},
"status": "pending",
"attempts": 0,
"next_attempt_at": 1699454445
}
For retryable, next_attempt_at and a bounded last_error describe the next
worker attempt. Witness outages, invalid responses, timeouts, and temporary
signing failures retry with bounded exponential backoff. The same persisted
tuple is signed on every attempt. If the immutable tuple reaches its signing
deadline, the job becomes terminal failed with the stable last_error value
signing_deadline_exceeded; its timestamp is never refreshed.
Batch and transparency-log order follows confirmation availability and batch
closure, not reservation time: each batch orders its eligible jobs by
(sequence, hash), while a lower-sequence job that confirms after an earlier
batch is appended in a later batch. This prevents reservation gaps or old
timestamps from stranding recovered jobs.
Breaking deployment migration
The attestation job API replaces the old timestamp API. There are no
/v1/timestamp routes and the bundled CLI no longer has timestamp or get
commands. Deploy the migrated gateway database and the new gateway/CLI together;
older clients receive 404 Not Found. Jobs left pending before a restart are
unlocked by migration or reclaimed after lease expiry and resumed by the worker.
Gateway admission and workers
The gateway requires a signing window at startup:
export WITNESS_JOB_SIGNING_WINDOW_SECS=300
The value must be greater than the 30-second worker lease. Expired jobs remain canonical failed duplicates: submitting the same hash again returns the failed job rather than creating a replacement tuple.
The window is measured from the reserved attestation timestamp. A job that
cannot complete before timestamp + window is terminally failed rather than
given a new timestamp. The failed job remains the canonical record for its
hash, so a later submission returns the same failed job rather than creating a
new tuple. WITNESS_ATTESTATION_WORKERS controls the bounded worker pool
(default 4, maximum 64), and WITNESS_MAX_ACTIVE_JOBS bounds active
pending/retryable jobs (default 1000). Existing duplicate hashes are returned
even when active-job capacity is full.
Configuration
Network Configuration (network.json)
{
"id": "my-network",
"threshold": 2,
"signature_scheme": "ed25519",
"witnesses": [
{"id": "witness-1", "pubkey": "...", "endpoint": "http://localhost:3001", "auth_token": "<random-secret>"},
{"id": "witness-2", "pubkey": "...", "endpoint": "http://localhost:3002", "auth_token": "<random-secret>"},
{"id": "witness-3", "pubkey": "...", "endpoint": "http://localhost:3003", "auth_token": "<random-secret>"}
],
"federation": {
"enabled": false,
"peers": []
},
"external_anchors": {
"enabled": false,
"providers": []
}
}
Signature Scheme
Ed25519 (default)
- Fast signing (~50μs)
- Multiple signatures stored (N × 64 bytes)
- Best for: low latency, few witnesses
Only Ed25519 multisignatures are supported. The signature_scheme value must be
ed25519; legacy BLS configurations and BLS aggregate receipts are unsupported
and rejected.
External Anchoring
Anchor batch merkle roots to external services for additional security:
{
"external_anchors": {
"enabled": true,
"anchor_period": 3600,
"providers": [
{"type": "internet_archive", "enabled": true},
{"type": "trillian", "enabled": true, "log_url": "https://..."},
{"type": "dns_txt", "enabled": true, "domain": "anchors.example.com"},
{"type": "blockchain", "enabled": true, "rpc_url": "https://...", "chain_id": 1}
]
}
}
Admin Dashboard Auth
Admin UI requires an API key when enabled:
# Option 1: CLI flag
WITNESS_JOB_SIGNING_WINDOW_SECS=300 \
cargo run --release -p witness-gateway -- \
--config examples/network.json \
--port 8080 \
--admin-ui \
--admin-api-key "replace-with-random-secret"
# Option 2: environment variable
export WITNESS_ADMIN_API_KEY="replace-with-random-secret"
export WITNESS_JOB_SIGNING_WINDOW_SECS=300
cargo run --release -p witness-gateway -- \
--config examples/network.json \
--port 8080 \
--admin-ui
For browser access, use HTTP Basic Auth (username: admin, password: <admin-api-key>).
For API access, send either:
Authorization: Bearer <admin-api-key>X-Admin-Key: <admin-api-key>
Production Deployment
Recommended Architecture
Deploy 3 independent networks across different datacenters. Clients query all gateways and require 2-of-3 agreement.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Gateway A │ │ Gateway B │ │ Gateway C │
│ Frankfurt │ │ Nuremberg │ │ Helsinki │
│ 3 witnesses │ │ 3 witnesses │ │ 3 witnesses │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└────────────────┼────────────────┘
▼
Client queries all 3
Requires 2-of-3 agreement
Hetzner Cloud Setup (~€18/month)
| Server | Location | Role |
|---|---|---|
| VPS 1 | Frankfurt | Gateway A + Witness B3 + Witness C2 |
| VPS 2 | Nuremberg | Gateway B + Witness C3 + Witness A2 |
| VPS 3 | Helsinki | Gateway C + Witness A3 + Witness B2 |
Each datacenter hosts witnesses from all networks—no single failure takes down any network.
Production Checklist
- 5-7 witnesses minimum per network
- Threshold > 50% (e.g., 4-of-7)
- TLS on all endpoints
- Federation with 2+ peer networks
- External anchoring enabled
- Database backups configured
- Admin dashboard enabled (
--admin-ui --admin-api-key ...)
Examples
Basic Network (Ed25519)
./examples/setup.sh
./examples/start.sh
./examples/demo.sh
Federation (3 Networks)
./examples/federation/setup.sh
./examples/federation/start.sh
./examples/federation/demo.sh
Security Model
Threats Mitigated
| Threat | Mitigation |
|---|---|
| Single witness compromise | Threshold signatures (N-of-M) |
| Signature forgery | Ed25519 cryptographic security |
| Timestamp manipulation | Multiple independent witnesses |
| Content exposure | Only hashes submitted |
| Network compromise | Federation + external anchoring |
| Denial of service | Per-IP/global limits plus deployment-level admission controls |
Residual Risks
- Gateway compromise: Mitigated by federation, not eliminated
- Clock manipulation: Requires threshold witnesses to collude
- Network partitions: No Byzantine fault tolerance
Performance
| Metric | Ed25519 |
|---|---|
| Typical worker signing pass | 50-150ms |
| Signature size (3 witnesses) | 192 bytes |
| Throughput | 100-500 req/s |
API submission latency is separate from signing latency: POST /v1/attestations
only durably reserves the tuple, and clients poll while workers obtain quorum.
FAQ
Q: What does "Anonymous Quorum" mean?
A: "Anonymous" because you submit only a hash—no accounts, no identity, no tracking. "Quorum" because multiple independent witnesses must sign before an attestation is valid. You get privacy and trust without a single point of failure.
Q: Is this really free?
A: For users, yes. Gateway operators pay gas costs only if they enable Ethereum anchoring (optional).
Q: Can witnesses see my data?
A: No. You only submit SHA-256 hashes, not the content itself.
Q: How is this different from a traditional timestamp authority?
A: Traditional TSAs require trusting a single party. Witness requires multiple independent parties to collude, and optionally anchors to public systems for additional verification.
License
Apache 2.0 - see LICENSE file for details.