Skip to content

About

Pure-Ruby OpenTimestamps client: Bitcoin-anchored timestamps with zero runtime dependencies

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

Repository files navigation

opentimestamps

A pure-Ruby client for OpenTimestamps, with no runtime dependencies. It stamps a hash against the Bitcoin blockchain through public calendar servers, upgrades the resulting proof to a block attestation, and verifies it, using only the standard library (digest, openssl, net/http, json). The proofs it produces are self-verifying and stay valid without this gem or any particular server.

The reference OpenTimestamps clients are Python and JavaScript; nothing equivalent is published for Ruby on RubyGems. This gem lets a Ruby app anchor and verify timestamps in-process, without shelling out to another language.

Install

gem "opentimestamps"

Usage

require "opentimestamps"

# Stamp: hashes the data (SHA-256), submits the digest to the default calendars,
# and returns a pending proof. Use stamp_digest to submit a precomputed hash and
# keep the content itself private.
ots = OpenTimestamps.stamp("hello world\n")

# Persist it. The calendar indexes each submission by a per-request commitment,
# so if you keep only the hash you can never upgrade. Always save the .ots.
File.binwrite("hello.txt.ots", ots.serialize)

# Upgrade, an hour or so later, once a calendar has anchored the commitment in a
# Bitcoin transaction. Returns true if the file changed.
ots = OpenTimestamps::DetachedTimestampFile.deserialize(File.binread("hello.txt.ots"))
OpenTimestamps.upgrade(ots) && File.binwrite("hello.txt.ots", ots.serialize)

# Verify against the chain. Raises VerificationError unless a block attestation
# matches; returns the confirmed attestations, each carrying the proven digest.
OpenTimestamps.verify(ots)
# => [#<struct Verification height=358391, time=2015-05-28 ..., digest="\x00..">]

OpenTimestamps.verified?(ots)   # boolean form; never raises

Compare verify's digest against the hash of your own file to bind the proof to your document.

Notes on the design

The parser treats its input as untrusted: malformed bytes, oversized length prefixes, and pathologically deep or large proofs all raise DeserializationError rather than a raw Ruby error, and depth and message-size caps bound the work a single proof can cause.

Verification goes through a Chain oracle (#block_merkle_root_and_time), so the bundled public-explorer adapter can be swapped for your own Bitcoin node when you want a check that trusts nothing external.

Attestation types the library does not model (Litecoin, Ethereum, or anything added later) are carried through untouched, so re-serializing a proof never corrupts it. Ethereum's Keccak-256 is implemented in pure Ruby for the proofs that need it; Bitcoin proofs never do.

The client never broadcasts a Bitcoin transaction. Calendars batch many digests into one; the client only submits and upgrades.

Interop

test/vectors/ holds twelve .ots files produced by the reference client (among them the timestamp of the Bitcoin whitepaper, plus multi-chain, unknown notary, and merkle-tree proofs). The suite asserts each one re-serializes byte-for-byte, and checks a proof against Bitcoin block 358391 using a recorded merkle root, offline. The reference ots client also reads proofs this gem creates.

Command line

The gem installs an ots executable, a thin wrapper over the library:

$ ots stamp report.pdf                 # writes report.pdf.ots (pending)
$ ots upgrade report.pdf.ots           # an hour later: fold in the Bitcoin path
$ ots verify report.pdf                # checks report.pdf.ots and binds it to the file
$ ots verify report.pdf.ots            # same, reference-client style (proof path)
$ ots info report.pdf.ots              # dump the proof's structure

verify recomputes the file's hash and confirms it matches the proof, so a success means this document is anchored, not just that the proof is valid. It can also require the proof to be anchored in several distinct Bitcoin blocks (--quorum 2) and check against your own node (see below).

Verifying against your own node

Chain::Explorer trusts a public explorer. For a check that trusts nothing external, point verification at your own Bitcoin Core:

chain = OpenTimestamps::Chain::BitcoinCore.new(
  "http://127.0.0.1:8332", user: "rpcuser", password: "secret"
)
OpenTimestamps.verify(ots, chain: chain, quorum: 2)

BitcoinCore.from_cookie(path) reads the node's .cookie file instead of a password. From the CLI: ots verify file --node http://127.0.0.1:8332 --cookie ~/.bitcoin/.cookie.

Roadmap

  • Byte-exact interop with reference .ots vectors, both directions.
  • Multi-calendar submit with merge (a single calendar being down is not fatal).
  • Chain::BitcoinCore JSON-RPC adapter (verification against your own node).
  • Quorum (m-of-n distinct Bitcoin blocks), enforced at verify.
  • CLI (ots stamp | upgrade | verify | info).

License

MIT.

About

Pure-Ruby OpenTimestamps client: Bitcoin-anchored timestamps with zero runtime dependencies

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages