Skip to main content

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

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 or MIT license at your option. Unless you explicitly state otherwise, any contribution you submit for inclusion shall be dual licensed as above, without additional terms.

Metadata

Release files for otskit 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for otskit 0.1.0
File Size Uploaded Provenance
otskit-0.1.0.tar.gz 81.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for otskit 0.1.0
File Interpreter ABI Platform
otskit-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 114.4 kB

Release files / otskit-0.1.0.tar.gz

Download URL otskit-0.1.0.tar.gz
Size 81.6 kB
Tags Source
SHA-256 checksum
How to use checksums
4ca89b8059fde1a41f6abb9d75181cb81ccb1cdcb8f9e95ecab4c7725f5faed0
BLAKE2b-256 checksum
How to use checksums
e504fd3f2def3df605b624f075925da44624b0b43e8afe2c7b3de679b15825dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / otskit-0.1.0-py3-none-any.whl

Download URL otskit-0.1.0-py3-none-any.whl
Size 32.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2fb9e4f03ed5c0560b20b740d625d8bede24fd6bc06f0098ac7f765f5ca95999
BLAKE2b-256 checksum
How to use checksums
462f75d81e552ec5a34ccf41ad9462b851e3780f1255eb0b462a22ed0c341b8c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for otskit 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page