By the time I had a look into Craig Wright’s blog post that seemed to imply that he is Satoshi , others had already pointed out that the signature was copied from a 2009 transaction . The contents of the "Sartre" file, however, were still a mystery. Dan Kaminsky had a blog post up analyzing the commands from CW’s post, but hadn’t been able to figure that bit out, so he asked me to have a look.
Following a screenshot of the output of
sha256sum Sartre , there’s a screenshot purportedly the file being displayed through a program called more , but only the first 14% can be seen, making it impossible to verify. How convenient . It’s also important to note that what CW signs is the raw sha256 of "Sartre" rather than the file itself. OpenSSL will sha256 that data it’s signing or verifying anyway, so this would normally be unnecessary step. More on that in a bit.
Given that the signature is valid, there are really very few possible explanations of what’s going on.
- CW has a computationally feasible preimage attack on sha256
- CW is Satoshi and has a been sitting on computationally feasible collision attack on sha256 since 2009
- CW is some sort of actual wizard who enjoys trolling cryptocurrency geeks.
- CW has pulled some sort of digital slight-of-hand
So, about that last one. Obviously, the signature presented was valid for that Bitcoin transaction, but where did the value
479f9dff0155c045da78402177855fdb4f0f396dc0d2c24f7376dd56e2e68b05 come from? Finding out requires digging a bit into the innards of Bitcoin. I was not feeling quite masochist enough to slog through a bunch of C++ code this morning, so I pulled up Vitalik Buterin’s pybitcointools . We need to know exactly what is being passed into ECDSA to verify a transaction. The function
def verify_tx_input(tx, i, script, sig, pub): if re.match('^[0-9a-fA-F]*$', tx): tx = binascii.unhexlify(tx) if re.match('^[0-9a-fA-F]*$', script): script = binascii.unhexlify(script) if not re.match('^[0-9a-fA-F]*$', sig): sig = safe_hexlify(sig) hashcode = decode(sig[-2:], 16) modtx = signature_form(tx, int(i), script, hashcode) return ecdsa_tx_verify(modtx, sig, pub, hashcode)
…the second to last line in this function being the critical one. What does
def signature_form(tx, i, script, hashcode=SIGHASH_ALL): i, hashcode = int(i), int(hashcode) if isinstance(tx, string_or_bytes_types): return serialize(signature_form(deserialize(tx), i, script, hashcode)) newtx = copy.deepcopy(tx) for inp in newtx["ins"]: inp["script"] = "" newtx["ins"][i]["script"] = script if hashcode == SIGHASH_NONE: newtx["outs"] =  elif hashcode == SIGHASH_SINGLE: newtx["outs"] = newtx["outs"][:len(newtx["ins"])] for out in newtx["outs"][:len(newtx["ins"]) - 1]: out['value'] = 2**64 - 1 out['script'] = "" elif hashcode == SIGHASH_ANYONECANPAY: newtx["ins"] = [newtx["ins"][i]] else: pass return newtx
It turns out that the actual data signed does not exactly match the transaction that is recorded in the blockchain. There are three reasons for this. The first is that it is not, in general, possible for a signature to contain itself. The second has to do with these "SIGHASH" flags. The details of how those work can be found in the documentation for OP_CHECKSIG , but it’s not important to understand them the relevant transaction. The third is that since the signature is spending a previous transaction’s output, it needs to include information about it. That’s what the parameters
i (identifies the input number for this transaction) and
script (the "scriptPubKey" from the transaction output being spent) are for.
Feeding the to the
# pybitcointools https://github.com/vbuterin/pybitcointools from bitcoin import * # output of # `bitcoin-cli getrawtransaction 828ef3b079f9c23829c56fe86e85b4a69d9e06e5b54ea597eef5fb3ffef509fe` tx = '0100000001ba91c1d5e55a9e2fab4e41f55b862a73b24719aad13a527d169c1fad3b63'+/ 'b5120100000049483045022100c12a7d54972f26d14cb311339b5122f8c187417dde1e'+/ '8efb6841f55c34220ae0022066632c5cd4161efa3a2837764eee9eb84975dd54c2de28'+/ '65e9752585c53e7cce01ffffffff0200ca9a3b00000000434104bed827d37474beffb3'+/ '7efe533701ac1f7c600957a4487be8b371346f016826ee6f57ba30d88a472a0e4ecd2f'+/ '07599a795f1f01de78d791b382e65ee1c58b4508ac00d2496b0000000043410411db93'+/ 'e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84'+/ 'ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000' # from # `bitcoin-cli getrawtransaction 12b5633bad1f9c167d523ad1aa1947b2732a865bf5414eab2f9e5ae5d5c191ba 1` spk = '410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c'+/ 'b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac' # create signature verification 'modified transaction' modtx = signature_form(tx, 0, spk, SIGHASH_ALL) # append the hashcode - in this case SIGHASH_ALL which is just 1 as a little-endian uint32 # see the txhash function modtx += hexlify(encode(SIGHASH_ALL, 256, 4)[::-1]) print modtx # 0100000001ba91c1d5e55a9e2fab4e41f55b862a73b24719aad13a527d169c1fad3b63b5120100 # 000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0 # eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3acffffffff0200ca9a # 3b00000000434104bed827d37474beffb37efe533701ac1f7c600957a4487be8b371346f016826 # ee6f57ba30d88a472a0e4ecd2f07599a795f1f01de78d791b382e65ee1c58b4508ac00d2496b00 # 00000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2 # e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000010000 # 00 # un-hex bin_modtx = changebase(modtx, 16, 256) print sha256(bin_modtx) # 479f9dff0155c045da78402177855fdb4f0f396dc0d2c24f7376dd56e2e68b05 with open('Sartre', 'w') as f: f.write(bin_modtx)
The "Sartre" file is, of course, available fordownload. I also posted a gist with the decoded transaction data from this file.
I mentioned that normally, when using ECDSA to sign or verify a file, it is unnecessary to hash it manually. This is where CW’s slight-of-hand lies. ECDSA computes the signature operation on a 256 bit integer referred to as z . Normally this is computed as sha 256( message ) , but Bitcoin does sha 256( sha 256( modtx )) . CW showed the signature verification using OpenSSL’s ECDSA on sha 256( modtx ) . OpenSSL’s does another sha256 on the data, which makes the z value match.