The PDF uses Python uploader/downloader scripts present in the Bitcoin blockchain itself
Those scripts are present at:
These are my forks of Ken Shirriff's Gists who presumably found them while researching for: http://www.righto.com/2014/02/ascii-bernanke-wikileaks-photographs.html
Both scripts have the copyright header:
# File downloader
# Requires git://github.com/jgarzik/python-bitcoinrpc.git
# (c) 2013 Satoshi Nakamoto All Rights Reserved
# UNAUTHORIZED DUPLICATION AND/OR USAGE OF THIS PROGRAM IS PROHIBITED BY US AND INTERNATIONAL COPYRIGHT LAW
which is likely a joke and not really written by Satoshi in my opninon.
Both uploader and downloader are uploaded with the uploader encoding, which creates a slight chicken-and-egg bootstrap issue to download the downloader. But the encoding is so simple that you could just download the raw downloader data and fix it up manually.
After downloading the downloader manually, I managed to run it to extract a byte-by-byte match of the PDF present at https://web.archive.org/web/20210418161957/https://bitcoin.org/bitcoin.pdf (sha256 == b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553).
First you have to be running a Bitcoin core server locally. Suppose that your
Then I run:
git clone git://github.com/jgarzik/python-bitcoinrpc.git
git -C python-bitcoinrpc checkout cdf43b41f982b4f811cd4ebfbc787ab2abf5c94a
pip install python-bitcoinrpc==1.0
BTCRPCURL=http://asdf:[email protected]:8332 \
python3 bitcoin-file-downloader.py \
where tx 54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713 (block 230009) is the transaction in which the whitepaper is encoded.
Encoding used by the downloader script
The downloader is not super strict about the subtleties of the uploader. We can immediately understand its format from the source:
data = b''
for txout in tx['vout'][0:-2]:
for op in txout['scriptPubKey']['asm'].split(' '):
if not op.startswith('OP_') and len(op) >= 40:
data += unhexlify(op.encode('utf8'))
length = struct.unpack('<L', data[0:4])
checksum = struct.unpack('<L', data[4:8])
data = data[8:8+length]
if checksum != crc32(data):
print('Checksum mismatch; expected %d but calculated %d' % (checksum, crc32(data)),
so we see that it:
- ignores the two last outputs
- ignores operands
- ignores any integer constant with less than 20 bytes (40 hex bytes)
Then, from the data above:
- the first 4 bytes are the payload length
- the next 4 bytes are a crc32 of the rest of payload data
- the payload starts at the 8th byte
Encoding used by the uploader script
The uploader is a bit more specific. Perhaps it is easiest to understand it by looking at the disassembly of a small upload, e.g. the downloader itself: https://www.blockchain.com/btc/tx/6c53cd987119ef797d5adccd76241247988a0a5ef783572a9972e7371c5fb0cc
First we have a bunch of outputs of type:
OP_1 data OP_3 OP_CHECKMULTISIG
and worth a tiny 0.00000001 BTC which contain the bulk of the data. These cannot presumably be spent, due to the
OP_CHECKMULTISIG, but this cannot be proven either.
Then at then end we have two different transactions, which as we saw above the downloader ignores:
a standard P2PKH
OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG with minimal value 0.00000001 BTC and unspent. TODO why? This corresponds to the part of the code:
# dest output
out_value = Decimal(sys.argv)
change -= out_value
txouts.append((out_value, OP_DUP + OP_HASH160 + pushdata(addr2bytes(sys.argv)) + OP_EQUALVERIFY + OP_CHECKSIG))
which shows that you just pass an arbitrary target value and address from your script.
a standard spent P2PKH change transaction with a considerable value, to recover leftovers from the input. It is sent to a new address owned by you that is created on the fly via the RPC call:
change_addr = proxy.getnewaddress()
txouts.append([change, OP_DUP + OP_HASH160 + pushdata(addr2bytes(change_addr)) + OP_EQUALVERIFY + OP_CHECKSIG])
Reading the source basically confirms what we see on the disassembly. Some other pointers follow.
data = open(sys.argv,'rb').read()
data = struct.pack('<L', len(data)) + struct.pack('<L', crc32(data)) + data
fd = io.BytesIO(data)
we see the:
- payload length
being added to the start of the file.
data = fd.read(65*3)
if not data:
r = pushint(1)
n = 0
chunk = data[0:65]
data = data[65:]
if len(chunk) < 33:
chunk += b'\x00'*(33-len(chunk))
elif len(chunk) < 65:
chunk += b'\x00'*(65-len(chunk))
r += pushdata(chunk)
n += 1
r += pushint(n) + OP_CHECKMULTISIG
we see the main data encoding loop. This clarifies from the disassembly:
OP_1 data OP_3 OP_CHECKMULTISIG
OP_1 is fixed at
OP_3 can be less than 3, it encodes how many 65 byte chunks were encoded, with a maximum of 3 chunks present on each output due to
The last payload transaction can therefore have
OP_3 depending on granularity.
What else was the uploader script used for and was it popular?
I have indexed every single blockchain transaction that follows the above format, notably having a matching CRC32 in the first 8 bytes.
I have also managed to download and interpret every single one of the transactions as described briefly at: https://cirosantilli.com/cool-data-embedded-in-the-bitcoin-blockchain/illegal-content-of-block-229k
Basically, very soon after the uploader/downloader were uploaded on 229991, besides the whitepaper, several uploads were made intentionally containing what some countries might consider illegal data, although none of them eye-popping for my crazy standards, and as we've seen so far, the Bitcoin blockchain has survived them for the time being as of 2021.
Since the illegal content was uploaded soon after the uploader, I find it exceedingly likely that it was uploaded by the same person who created the uploader.
And after a short flurry of activity, the uploads stopped, and only one single upload was made much later at tx 89248ecadd51ada613cf8bdf46c174c57842e51de4f99f4bbd8b8b34d3cb7792 which caught my attention because it contains an ASCII art of a Buddha between two Yin Yang symbols as described at: https://cirosantilli.com/cool-data-embedded-in-the-bitcoin-blockchain/ascii-art
6969 696969 696969 6969
969 69 6969696 6969 6969 696
969 696969696 696969696969 696
969 69696969696 6969696969696 696
696 9696969696969 969696969696 969
696 696969696969 969696969 969
696 696 96969 _=_ 9696969 69 696
9696 969696 q(-_-)p 696969 6969
96969696 '_) (_` 69696969
96 /__/ \ 69
69 _(<_ / )_ 96
6969 (__\_\_|_/__) 9696
Therefore my conclusion is that the uploader and downloader scripts simply never reached considerable popularity.
They are just too over-engineered and clunkier than needed. Why would you need a CRC32 when the blockchain itself is already hashed byte-by-byte to the brim? Perhaps it was intended simply as a marker of "interesting information". But much more natural and dominant later on was to use ASCII strings, or simply magic bytes of certain file formats as markers, see e.g. my comments on more directly encoded images at: