Using Open Timestamps with Git
Created by Steven Baltakatei Sandoval on 2022-04-22T15:00+00 under a CC BY-SA 4.0 license and last updated on 2023-05-31T19:08+00. .
Edit(2023-03-04T14:22+00):Updated ikiwiki blog repository URL. Edit(2023-05-31):Add reboil.com wiki links.
Summary
I learned how to configure git
to automatically create Open
Timestamps proofs whenever I sign a commit with my OpenPGP key.
Background
Although Bitcoin is mostly popular for its use as digital money, in order for it to properly function in a decentralized manner, it must also act as a timestamping service to itself. Bitcoin does this by requiring miners to mark each candidate block they produce with a timestamp; accuracy is enforced by some rules that cause a block to be rejected. These rules can be roughly summarized as:
- The timestamp must be in the future (Median Past Time rule)
- The timestamp must not be too far into the future (Future Block Time rule)
Therefore, provided that the majority of miners are honest (an assumption Bitcoin already requires), the most recent block will likely have a timestamp that can be expected to be accurate to within a few hours.
OpenTimestamps is a timestamping service and set of programs that relies upon this internal timestamping feature of the Bitcoin blockchain. Open Timestamps was created by Peter Todd, a former Bitcoin Core developer. I believe I first heard about Open Timestamps (OTS) while following discussions on the /r/Bitcoin subreddit (possibly from this thread).
The timestamp service works by having a "calendar" server program
collect file hashes submitted from client programs at the direction of
some users. The hash of each user's file(s) is integrated into a
merkle tree by the server. Then, periodically, the calendar server
will create and submit a Bitcoin transaction containing the tree's
merkle root. Once the transaction is included in the Bitcoin
blockchain, the calendar server can then provide a client with the
merkle branch leading from the merkle root to the file hash the client
submitted; the merkle branch is typically encoded in an .ots
file
created next to the hashed file; this .ots
file permits the client,
armed with a copy of the blockchain, to verify that the hashed file
existed at least as far back as the timestamp of the block containing
the merkle root.
Using OpenTimestamps (OTS)
It is possible to use the opentimestamps.org
website to create the
.ots
file using only a web browser. See the "STAMP & VERIFY" section
of the main webpage.
However, client software is also provided in the Python, Javascript, and Java programming languages. Personally, I used the Python implementation which the examples later in this blog post will reference.
Installation
On a fresh Debian-based system, the Python implementation of OTS can be installed via:
$ sudo apt install python3-pip
$ pip3 install opentimestamps-client
The ots
and ots-git-gpg-wrapper
(to be used later) executables
then should be added to the PATH
environment variable. This can be
done by adding this code to your $HOME/.profile
file (or whichever
file you use to automatically load custom environment variables;
e.g. $HOME/.bashrc
):
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
export PATH="$HOME/.local/bin:$PATH"
fi
Running source ~/.profile
(or whichever sh
file contains the above
code) will then modify PATH
. Environment variables set in your
current shell can be found by running $ printenv
.
OTS CLI Usage
The command line interface for OTS version 0.7.0
works like this:
You want to timestamp a file named THESIS.md
. You do can do this by
running:
$ ots stamp THESIS.md
This will create a THESIS.md.ots
file adjacent to THESIS.md
.
However, this .ots
file does not contain the full merkle branch data
necessary, meaning the calendar server(s) used by ots
(several are
configured by default) will have to be contacted in order to upgrade
the .ots
file.
An .ots
file can be upgraded via:
$ ots upgrade THESIS.md.ots
If you are okay with the command waiting the (typically) several hours required for a calendar server to provide merkle branch data before exiting, then you can run this from the start:
$ ots --wait stamp THESIS.md
A .ots
can be verified by running:
$ ots verify THESIS.md.ots
In any case that results in downloading a proof from a calendar
server, ots
will save a cache of the merkle branch data in the
$HOME/.cache/ots
directory. ots
looks for proof data here before
contacting a calendar server. Also, I suspect that because ots
only
ever adds files to $HOME/.cache/ots
and never modifies files there,
multiple machines running the same version of ots
can use a file
synchronization tool such as syncthing
to merge their OTS caches for
future reference.
I have used this procedure to timestamp some of my own files for my own amusement and reference.
OTS Git Wrapper
The reason I created this blog post, aside from documenting my own
usage of ots
, was to document how I started using the
ots-git-gpg-wrapper
feature.
git
(distributed version control software I use in this blog, my
Notable Public Keys book, and other projects) can be configured to use
ots
to improve signed commits. The OTS documentation for this
feature can be found within the OTS GitHub repository.
Typical Usage
To summarize, the feature can be activated for all git repositories by running:
$ path_wrapper="$HOME/.local/share/ots/ots-git-gpg-wrapper.sh" $ git config --global gpg.program "$path_wrapper"
If you want to only activate this feature for a single repository, change the working directory to the repository, replace
--global
to--local
, then run the command, like so:$ cd $HOME/some_git_repo $ path_wrapper="$HOME/.local/share/ots/ots-git-gpg-wrapper.sh" $ git config --local gpg.program "$path_wrapper"
The default contents of
ots-git-gpg-wrapper.sh
are:#!/bin/sh # Wrapper for the ots-git-gpg-wrapper # # Required because git's gpg.program option doesn't allow you to set command # line options; see the doc/git-integration.md ots-git-gpg-wrapper --gpg-program "`which gpg`" -- "$@"
Advanced Usage
To go further, I modified the original wrapper script
ots-git-gpg-wrapper.sh
to contain:#!/bin/sh # Wrapper for the ots-git-gpg-wrapper # # Required because git's gpg.program option doesn't allow you to set command # line options; see the doc/git-integration.md # Check if gpg is alias. (see https://unix.stackexchange.com/a/288513 ) if alias gpg 2>/dev/null; then ## Get gpg alias command gpg_cmd="$(type gpg)"; # get raw alias definition gpg_cmd="${gpg_cmd#*=\'}"; # trim chars before and including first apostrophe gpg_cmd="${gpg_cmd%\'*}"; # trim chars after and including last apostrophe else gpg_cmd="$(which gpg)"; fi; # Check if jsonrpc_option file available path_jsonrpc_option="$HOME/.local/share/ots/jsonrpc_option.txt"; if [ -f "$path_jsonrpc_option" ]; then jsonrpc_option="$(cat "$path_jsonrpc_option" | head -n1)"; else jsonrpc_option=""; fi; eval "ots-git-gpg-wrapper $jsonrpc_option --gpg-program $gpg_cmd -- $@"
To add the
--wait
option when runningots-git-gpg-wrapper
, I copiedots-git-gpg-wrapper.sh
to an adjacent file namedots-git-gpg-wrapper-wait.sh
containing:#!/bin/sh # Wrapper for the ots-git-gpg-wrapper # # Required because git's gpg.program option doesn't allow you to set command # line options; see the doc/git-integration.md # Check if gpg is alias. (see https://unix.stackexchange.com/a/288513 ) if alias gpg 2>/dev/null; then ## Get gpg alias command gpg_cmd="$(type gpg)"; # get raw alias definition gpg_cmd="${gpg_cmd#*=\'}"; # trim chars before and including first apostrophe gpg_cmd="${gpg_cmd%\'*}"; # trim chars after and including last apostrophe else gpg_cmd="$(which gpg)"; fi; # Check if jsonrpc_option file available path_jsonrpc_option="$HOME/.local/share/ots/jsonrpc_option.txt"; if [ -f "$path_jsonrpc_option" ]; then jsonrpc_option="$(cat "$path_jsonrpc_option" | head -n1)"; else jsonrpc_option=""; fi; eval "ots-git-gpg-wrapper --wait $jsonrpc_option --gpg-program $gpg_cmd -- $@"
Part of these modifications take into account the fact that I use the
alias
function to include custom options whenever I rungpg
.Additionally, my modifications include permitting
ots
commands run from my main GNU/Linux Debian-based workstation that is NOT a bitcoin node to connect to a bitcoin node using the--bitcoin-node
option for theots-git-gpg-wrapper
command. I do this by loading into a variable the first line of a file namedjsonrpc_option.txt
that I maintain alongside theots-git-gpg-wrapper.sh
file. The first line of thejsonrpc_option.txt
file contains information used to permit connection to a bitcoin node (a Raspiblitz in my case) on the local network at IP address192.168.0.4
via the RPC interface on port 8332 using usernameraspibolt
and passwordhunter2
. The file contents take the form:--bitcoin-node http://raspibolt:hunter2@192.168.0.4:8332/
With those changes in place and the
ots-git-gpg-wrapper.sh
wrapper script activated via agit config
command mentioned earlier, if I create a signed commit and then rungit log
, I will see:commit 626303d06805ee6cd50e231da6988fc0235bd8e8 (HEAD -> master) ots: Calendar https://btc.calendar.catallaxy.com: Pending confirmation in Bitcoin blockchain ots: Calendar https://finney.calendar.eternitywall.com: Pending confirmation in Bitcoin blockchain ots: Calendar https://alice.btc.calendar.opentimestamps.org: Pending confirmation in Bitcoin blockchain ots: Calendar https://bob.btc.calendar.opentimestamps.org: Pending confirmation in Bitcoin blockchain ots: Could not verify timestamp! gpg: Signature made Fri 22 Apr 2022 05:58:35 PM GMT gpg: using RSA key 38F96437C83AC88E28B7A95257DA57D9517E6F86 gpg: Good signature from "Steven Sandoval <baltakatei@gmail.com>" [ultimate] gpg: aka "Steven Sandoval <baltakatei@alumni.stanford.edu>" [ultimate] gpg: aka "[jpeg image of size 1846]" [ultimate] Primary key fingerprint: 3457 A265 922A 1F38 39DB 0264 A0A2 95AB DC34 69C9 Subkey fingerprint: 38F9 6437 C83A C88E 28B7 A952 57DA 57D9 517E 6F86 Author: Steven Baltakatei Sandoval <baltakatei@gmail.com> Date: 2022-04-22T17:57:30+00:00 draft(posts:20220422):using ots with git - note: testing timestamp feature with example commit
After three hours, this message changed to:
commit 626303d06805ee6cd50e231da6988fc0235bd8e8 (HEAD -> master) ots: Calendar https://btc.calendar.catallaxy.com: Pending confirmation in Bitcoin blockchain ots: Calendar https://finney.calendar.eternitywall.com: Pending confirmation in Bitcoin blockchain ots: Calendar https://alice.btc.calendar.opentimestamps.org: Pending confirmation in Bitcoin blockchain ots: Got 1 attestation(s) from https://bob.btc.calendar.opentimestamps.org ots: Success! Bitcoin block 733047 attests existence as of 2022-04-22 GMT ots: Good timestamp gpg: Signature made Fri 22 Apr 2022 05:58:35 PM GMT gpg: using RSA key 38F96437C83AC88E28B7A95257DA57D9517E6F86 gpg: Good signature from "Steven Sandoval <baltakatei@gmail.com>" [ultimate] gpg: aka "Steven Sandoval <baltakatei@alumni.stanford.edu>" [ultimate] gpg: aka "[jpeg image of size 1846]" [ultimate] Primary key fingerprint: 3457 A265 922A 1F38 39DB 0264 A0A2 95AB DC34 69C9 Subkey fingerprint: 38F9 6437 C83A C88E 28B7 A952 57DA 57D9 517E 6F86 Author: Steven Baltakatei Sandoval <baltakatei@gmail.com> Date: 2022-04-22T17:57:30+00:00 draft(posts:20220422):using ots with git - note: testing timestamp feature with example commit
The line that matters is the one mentioning block 733047 (probably this transaction, based on the current state of the "bob" opentimestamps.org calendar server).
ots: Success! Bitcoin block 733047 attests existence as of 2022-04-22 GMT
If I run
git log
another time, theots:
lines change to indicate the local cache is being used to verify instead of a calendar server.commit 626303d06805ee6cd50e231da6988fc0235bd8e8 (HEAD -> master) ots: Got 1 attestation(s) from cache ots: Success! Bitcoin block 733047 attests existence as of 2022-04-22 GMT ots: Good timestamp gpg: Signature made Fri 22 Apr 2022 05:58:35 PM GMT gpg: using RSA key 38F96437C83AC88E28B7A95257DA57D9517E6F86 gpg: Good signature from "Steven Sandoval <baltakatei@gmail.com>" [ultimate] gpg: aka "Steven Sandoval <baltakatei@alumni.stanford.edu>" [ultimate] gpg: aka "[jpeg image of size 1846]" [ultimate] Primary key fingerprint: 3457 A265 922A 1F38 39DB 0264 A0A2 95AB DC34 69C9 Subkey fingerprint: 38F9 6437 C83A C88E 28B7 A952 57DA 57D9 517E 6F86 Author: Steven Baltakatei Sandoval <baltakatei@gmail.com> Date: 2022-04-22T17:57:30+00:00 draft(posts:20220422):using ots with git - note: testing timestamp feature with example commit
If I had instead activated
ots-git-gpg-wrapper-wait.sh
, then thegit commit
operation would wait until it received a proof from a calendar server before exiting. This may be useful for for major signed commits and tags where it is desirable to transmit the proof via the git repository itself instead of relying upon a calendar server or the cache files being available in the future. I don't know of a method to upgrade the OTS proof data already included in a git repository.
Use Cases
.ots
files
Generating .ots
files is easier than messing with the OTS git
wrapper script since no knowledge of git
is required. For this
reason, I imagine timestamping files via Python or the
OpenTimestamps.org website would be more common.
Some use cases I can imagine are:
- A reporter timestamping
.eml
files containing controversial correspondence. - A lawyer timestamping controversial secret
.pdf
documents that will be revealed at a later time. - A civil engineer timestamping a receipt of a
.pdf
report they create for a building owner about the need to perform expensive repairs. - A business owner timestamping a contract encoded in a
.pdf
to help prove when an agreement was made. - A speedrunner wanting to prove that a world record they recorded in
a
.mkv
video file was made by a certain date in the past. - A software developer wanting to prove that certain archived versions of their software existed as early as a certain date.
OTS git wrapper
In the abstract, the OTS git wrapper allows me to prove the existence of files in a git repository that I already am using OpenPGP to sign. This is useful for avoiding ambiguities associated with the possibility of some attacker capturing my OpenPGP private key and signing files with forged timestamps in order to try and convince someone to believe their revised history of some event.
Although I personally am not in the business of proving the existence
of files for others, I can imagine some software developers who use
git
who may want to prove who had created a piece of code before a
copycat.
Also, git
, because it is version control software for file trees,
can be used to prove the existence of many files at a time (albeit via
SHA1 which, as of 2022, is the best method git
supports for hashing
data). This may be useful if the number of proofs I need to create is
large enough to saturate a significant fraction of public calendar
server bandwidth and storage capacities.
Caveats
An OTS timestamp can only prove data existed after a certain date. It cannot prove exactly when data was created.
Also, an attacker could pregenerate a large fraction of all possible
permutations of data and create an OTS proof for each permutation. For
example, if I wanted to bamboozle people into thinking I could predict
the weather anywhere in the world 8 years in advance, I could create a
large number of text files containing permutations of the general
prediction "I predict in 2022 that on [date in 2030], in [location],
it will be [weather type]." I would then create .ots
proof files for
each prediction and sit on them for 8 years. Then, on the specified
date in 2030, I would selectively reveal only the predictions that
happened to be true.
Conclusion
OpenTimestamps can be used to prove the existence of files and git commits in a scalable manner using the Bitcoin blockchain.