Metadata-Version: 2.5
Name: otskit
Version: 0.1.0
Summary: OpenTimestamps (.ots) reader, writer, calendar client and verifier — standard library only
Author: Lelimo Capital sp. z o.o.
License-Expression: MIT OR Apache-2.0
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
License-File: NOTICE
Keywords: bitcoin,opentimestamps,ots,proof,timestamp
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# otskit

OpenTimestamps proofs with nothing but the Python standard library: read and
write `.ots` files byte for byte, stamp through the public calendars, upgrade
promises to Bitcoin anchors, and check an anchor against a block header.

```
pip install otskit          # or: copy src/otskit anywhere; it has no dependencies
otskit stamp report.pdf     # -> report.pdf.ots, a calendar promise
otskit upgrade report.pdf.ots   # an hour or two later: the Bitcoin anchor
otskit info report.pdf.ots
otskit verify report.pdf.ots --header <80-byte header hex from your node>
```

Python 3.10 or later. No third-party packages, no compiled code, no OpenSSL
features beyond SHA-256: RIPEMD-160 and Keccak-256 fall back to plain-Python
implementations when the OpenSSL build lacks them (OpenSSL 3 without the
legacy provider has no RIPEMD-160, and every Bitcoin-anchored proof uses it).

## Why another OpenTimestamps library

The official client works, and this is not meant to replace it. It needs a
local Bitcoin Core to verify, and its library is LGPL; the JavaScript library
has not been released since 2021. otskit is:

* **dependency-free** — a verifier can be copied onto a machine with nothing
  but Python and audited in an afternoon (about 1,700 lines, half of them
  explanation);
* **upstream-compatible, and tested as such** — every example proof from the
  official repositories is read, written back byte for byte, and compared
  attestation by attestation with what the official `python-opentimestamps`
  library reads; `otskit info` prints exactly what `ots info` prints;
* **explicit about what is verified** — see below.

## What `verify` checks, and what it does not

`verify` recomputes the file's digest, walks every branch of the proof, and for
each Bitcoin attestation compares the message it arrives at with the merkle
root in a block header:

| Header source | Flag | What a `verified` means |
|---|---|---|
| you, from your own node: `bitcoin-cli getblockheader <hash> false` | `--header HEX` | your node agrees |
| an Esplora server (Blockstream, mempool.space, your own) | `--esplora URL` (off by default, repeatable) | that server agrees; several servers = several opinions |
| none | — | nothing: the answer is `unchecked`, and the height and merkle root are printed for you to compare |

For every header used, otskit also checks that it hashes below the target its
own `nBits` declares, and — for Esplora — that the header hashes to the block
hash the server named for that height.

**This is not SPV.** No chain of headers is followed from a checkpoint, no
difficulty retargeting is checked, and nothing establishes that the header is
part of the most-work Bitcoin chain beyond the word of its source. A header
with valid-looking proof of work at an attacker-chosen low difficulty would
pass the work check. If you need independence from any server, take the
header from your own node. Header-chain verification is the next milestone
(see Roadmap).

Exit codes (one word on stdout, details on stderr, `--json` for a report):

| Code | Word | Meaning |
|---|---|---|
| 0 | `verified` | every header consulted holds the proof's merkle root |
| 1 | `mismatch` | the file is not the stamped one, or a header for the attested height disagrees |
| 2 | `unusable` | the proof or a header cannot be read or fetched |
| 3 | `pending` | only calendar promises — not yet a proof of time |
| 4 | `unchecked` | anchored, but no header source was given |

## Stamping and privacy

`stamp` hashes the file (SHA-256), appends a 16-byte random nonce, hashes again,
and sends only that to each calendar — the same construction as the official
client. The calendar never sees the file's digest; the nonce stays inside the
`.ots`. The default calendars are the ones the official client uses
(`alice`, `bob`, `finney`); `--calendar` replaces them.

`upgrade` only contacts calendars on a list (the defaults plus `--calendar`):
the URL inside a pending attestation comes from the proof, and a proof is
untrusted input. The previous file is kept as `.ots.bak`.

## Library

```python
from otskit import parse, serialize, evidence, file_digest, stamp_digest
from otskit.bitcoin import Header, check

proof = parse(open("hello-world.txt.ots", "rb").read())
assert proof.digest == file_digest(open("hello-world.txt", "rb").read(), proof.hash_op)
for item in evidence(proof):
    print(item.kind, item.height or item.uri, item.msg.hex())
```

* `otskit.proof` — `parse`, `serialize`, `evidence`, `pending_of`,
  `replace_pending`, `stamp_digest`, `merge`, `submission`;
* `otskit.calendar` — `submit`, `upgrade` (`urllib`, 10-second timeouts);
* `otskit.bitcoin` — `Header`, `check`, `esplora_header`, `transaction_id`;
* `otskit.hashes` — `sha256`, `sha1`, `ripemd160`, `keccak256`.

## Limits

* Bitcoin only. Litecoin and the old Ethereum attestations are read, printed
  and preserved, never verified. Unknown attestations are preserved byte for
  byte.
* No header-chain (SPV) verification; see above.
* Parsing limits follow the official library: 256 levels of nesting, 4096-byte
  messages, 8192-byte attestation payloads. Real proofs run to about 100
  levels (`bitcoin.pdf.ots` is 101).
* `stamp` sends one request per file per calendar; it does not batch many
  files into one merkle tree as `ots stamp` does.

## Tests

```
pip install pytest && pytest           # offline, ~5 seconds
pytest -m network                      # also asks the real Esplora servers
```

`tests/vectors/` holds the upstream example proofs with expected values from
the official library, three of them upgraded against the live calendars, and
the real block headers of every anchored height (see
`tests/vectors/upstream/PROVENANCE.md`).

## Roadmap

1. **Header-chain verification without a full node** (opentimestamps-client
   issue #93, open since 2018): checkpoints, headers from several independent
   sources, linkage, proof of work and retargeting, optionally from genesis.
2. A shared test-vector format for all OpenTimestamps implementations,
   proposed upstream.
3. Calendar monitoring: availability and time-to-anchor of the public calendars.

## Origin and licence

otskit was extracted from Evix, where it timestamps batches of attestations in
Bitcoin; it was rewritten from the format description and does not contain
code from the LGPL OpenTimestamps libraries.

Copyright 2026 Lelimo Capital sp. z o.o. Licensed under either of
[Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at
your option. Unless you explicitly state otherwise, any contribution you submit
for inclusion shall be dual licensed as above, without additional terms.
