Note: You are viewing this blog post without the intended style information, which may result in formatting issues.
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 verify_tx_input
contains:
1 2 3 4 5 6 7 8 9 10 | 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
signature_form
do?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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[1], 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 for 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 signature_form
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | # 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 for download. 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 sha256(message), but Bitcoin does sha256(sha256(modtx)). CW showed the signature verification using OpenSSL's ECDSA on sha256(modtx). OpenSSL does another sha256 on the data, which makes the z value match.
[1] | It's possible to construct ECDSA signatures that contain themselves, but the algorithm to do this works, essentially, by randomly generating a signature, then working backwards to compute a keypair for it. For reasons outside the scope of this blog post, this cannot be used for Bitcoin transactions. |