Skip to content
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

BTC Apple Photos Timestamper


Pull all of your photos out of the Apple Photos app, hash them into a merkle tree, and put the root into Bitcoin.


Ostensibly so you can prove later that you have an undoctored photo that's been provably timestamped before someone else's version of the same. Actually, it's for funzies. I was expecting to do some (but hopefully not all) of the bitcoin-y side of the work, but then I found btctxstore. So I gave that a little bit of love and used it instead. Its underlying dependency, PyCoin, does not appear to support segwit addresses, and I didn't want to open up that can of worms just yet. So, thank goodness for Bitcoin's culture of backwards compatibility. Also, I wanted to play around with Click and Pipfile, both of which I'm totally loving.


First things first, you need to install this into a local Python3.6 environment. I'm loving on pipenv right now, so use that! Or whatever you want.

There's a bunch of shell commands under timestamper. I tried to make lots of sensible defaults (e.g. that your Apple photos library is at /Users/<yourusername>/Pictures/Photos Library.photoslibrary. If you're feeling really brave (and I don't think I'd recommend that), you can do the whole shebang in a single go:

timestamper end_to_end --change_address=<an address you control> --live --mainnet

You'll be prompted for your private key in Wallet-Import-Format (with your typing hidden) to lookup your UTXOs and to sign the transaction.

You can also do it in a few steps to watch the process and inspect the temporary files (saved in ./tmp).

  1. Inspect your photos library to get a list of all of the photo files to include in the merkle tree.
timestamper import_photos --library=/path/to/Photos Library.photoslibrary
  1. Get the Sha256 hash of each of those photos. This is the longest running step. It's parallelized to use all of the available cores.
timestamper hashify
  1. Build the merkle tree and run through a merkle proof to verify that it's working as expected.
timestamper build_and_verify_merkle
  1. Send the merkle root to Bitcoin. If you're doing it the slow way, let's do this in a few steps.

    4a. For starters, a dryrun to the testnet. And again, you'll be prompted for your private key.

    timestamper send_merkle_root_to_bitcoin --change_address=<an address you control> --dryrun --testnet

    4b. Now let's do a live run to the testnet

    timestamper send_merkle_root_to_bitcoin --change_address=<an address you control> --live --testnet

    4c. Dry run to mainnet. Don't forget to use mainnet addresses

    timestamper send_merkle_root_to_bitcoin --change_address=<an address you control> --dryrun --mainnet

    4d. Do it live!

    timestamper send_merkle_root_to_bitcoin --change_address=<an address you control> --live --mainnet

check your work in a python shell

from app.transmit import get_from_bitcoin

expected_merkle_root = 'f19a3f9912ac3f8572e4bff1a2476e47a5a751b74ac2acda11c702f6fc3997ca'
btc_transaction_id = 'b801e656beec4e2b057334068661bd7a503ad4bf8e8b635e52c73971ec55f58d'
actual_merkle_root = get_from_bitcoin(btc_transaction_id, is_testnet=False)
assert actual_merkle_root == expected_merkle_root


I'm not sure why you would, but thanks!

run the tests:

make test


timestamp all of your apple photos on the blockchain



No releases published


No packages published