In this post I am starting a short series of posts where I look into exploring the concept of a simple Bitcoin Quantum Computing kanarie bot.
Before getting into the details of how this kanarie bot might work, a little bit of background of what exactly this kanarie would try to sense.
Quantum computers today are small, way too small to attack cryptography, and way too small to mouns a quantum blockchain heist like the one I wrote about in my novel Ragnarok Conspiracy. In a number of years though, quantum computers will be large enough in terms of logical qubits, something like 1500 logical qubits, to slice through a public key and derive the private key. This is bad news if you encrypted your hard kept secrets and someone manages to decrypt them, but it is bad news too if (like in HIVE) your public signing key is actually public and someone can take over everything that signing key grants you ownership off.
For ECDSA elyptic curve signing, this slicing through a public key to find the private key could be achieved by an adjusted version of Shor's algoritm. To illustrate how early we still are in this, today this was posted on 𝕏-Twitter, showing that the number 1031167 was succesfully factored on a 11 logical qubits, running on 127 physical qubits.
HIVE is very vulnerable to future quantum computer attacks right now, and my spare time project CoinZdense will try to do something about that, not just for HIVE but for Web 3.0 technology in general, but this series isn't about that. This series is about BitCoin.
While Bitcoin, when used without key reusage in theory is less vulnerable than HIVE because USO's are spent in their entirety, and key-reuse should not be needed, key-reuse exists, even if it has diminished a bit since early days, and enough funds exists in USOs that are linked to exposed public keys to pose a significant economic risk for Bitcoin. So even if your bitcoin may be safe, the value of your bitcoin isn't. In this series I'm going to walk you throug the how and why of this, and we are going to look into the posibilities of a Bitcoin Quantum Kanarie bot.
Just some usefull imports
Before I start sharing some basic code, lets start off with a few imports I used.
import subprocess
import json
import os
import httpx
import functools
import datetime
import base58
from mbedtls import hashlib
The subprocess will be needed to call the bitcoin CLI, once, so we can find out where bitcoin core is installed and fetch the username and password we need for using the JSON-RPC API. The json and datetime imports are obvious. We need to import OS for basic path manipulations, and the httpx lib for the HTTP protocol that underlies the JSON-RPC API. The functools lib we will see is very usefull to keep us from writing lots of proxy code, mirroring the JSON-RPC API in our python code, we don't want to do that. Adresses for bitcoin use base58 encoding that we will need, and because versiona of hashlib have deprecated a hashing algo we are going to need, we choose to import an alternative hashlib API implementation from mbedtls.
Running a bitcoin node and talking to it.
In order to gather the info we need to run a kanarie bot in the future, the first step we need to take is to run a bitcoin core node and sync it so we can start to communicate to it. You can fetch a copy of bitcoin core for your OS and start the bitcoin deamon and waut for it to synchonize the entire blockchain. This first step will take quit a few hours. On my limited bandwith fiber connection it took almost two days.
After this couple of days though, we have a bitcoin node that we can talk to. Taling to bitcoin core requires talking JSON-RPC to the node with some basic authentication. So I wrote a trivial little class for doing just that in Python.
class JsonRpc:
def __init__(self):
res = subprocess.run(["bitcoin-core.cli", "getrpcinfo"], stdout=subprocess.PIPE)
with open(
os.path.join(
os.path.dirname(
json.loads(res.stdout)["logpath"]
), ".cookie"
),
"r") as cookiefile:
user, passw = cookiefile.read().split(":")
auth = httpx.BasicAuth(username=user, password=passw)
self.client = httpx.Client(auth=auth)
self._id = 0
def _method(self, method, *args):
self._id += 1
request = json.dumps(
{
"jsonrpc": "1.0",
"id": self._id,
"method": method,
"params": list(args)
})
response = self._client.post(
"http://127.0.0.1:8332/",
data=request,
timeout=120.0).json()
if response["error"] is None:
return response["result"]
raise RuntimeError(response["error"])
def __getattr__(self, method):
return functools.partial(self._method, method)
This class has a constructor that calls the bitcoin-core.cli directly in order to detect the place my system has bitcoin core installed so it can find and open the .cookie file, in order to read the running bitcoin core's username and password for the JSON-RPC interface. After this it creates an BasicAuth object for the HTTP client so it can create a client.
In order not to have ti implement a forwarder for each and every JSON-RPC method the bitcoin core API might expose, the getattr method is there to try and implement every unimplemented method by leveredging the functools library so that all method invovations get handled now by the _method method. This method constructs the JSON-RPC request and sends it to the bitcoin core API. If the call is succesfull, tt returns the result, if it isn't, it raises a runtime error.
So now we have the basic python code to talk to bitcoin core. Lets see what we can do with it.
Getting the hash of the first block
Now that we have the base code for accessing the JSON-RPC bitcoin core API, the first thing we are going to do is instanciate our JsonRpc client and get the block hash of the very first block in the chain. We set the block count (height) to zero, and from that point we are ready to start fetching and processing all of the blocks in the blockchain.
rpc = JsonRpc(username, password)
bhash = rpc.getblockhash(0)
count = 0
For a blockchain this old, especialy "the" blockchain this old, it is surprising how small the chain actually is compared to HIVE.
Using and interpreting 'getblock'
The core of our work in this first step towards a kanarie bot revolves around the getblock command.
The command takes two arguments, the blockhash of the block requested, and the verbosity. Because we are interested primaraly in the transactions, we need to set verbosity to the maximum for our purposes.
Much of the vervose block isn't that relevant for us, but the following parts are of special interest:
- time : The date and time of this block
- nextblockhash : The blockhash needed to fetch the next block
- tx[] : An array of transactions
- vin[] : Array of input USOs
- prevout: Info about the previous transaction where this inut was an output
- scriptPubKey : Information about the address or pubkey
- prevout: Info about the previous transaction where this inut was an output
- vout[] : Array of output USOs
- value : The value of this new output
- scriptPubKey : Information of the address or pubkey
- vin[] : Array of input USOs
What we are going to do is extract the bitcoin address and both spent events and receive events for that address. We record the time and for receive events specifically, also the amount. This should be everything we need for step two. We aren't going to look at special addresses for now, just the regular once. I won't go into the diference between the types of adresses, just know that for our direct purposes these regular adresses are enough for now.
Pubkeys to adresses
In order to normalize our output, we are going to convert all pubkey we encounter in scriptPubKey fields into adresses. The below piece of code does just that.
key = bytes.fromhex(hexkey)
hash1 = hashlib.sha256()
hash2 = hashlib.new('ripemd160')
hash1.update(key)
hash2.update(hash1.digest())
core = b"\x00" + hash2.digest()
hash3 = hashlib.sha256()
hash4 = hashlib.sha256()
hash3.update(core)
hash4.update(hash3.digest())
return base58.b58encode(core + hash4.digest()[:4]).decode()
Extracting address receive and spent events
Now we get to the bit of code that extracts the info we are going to be needing for step two of our data gathering stap for creating our kanarie.
while bhash:
block = rpc.getblock(bhash, 3)
bhash=None
tim =datetime.datetime.fromtimestamp(block["time"]).isoformat()
if "nextblockhash" in block:
bhash = block["nextblockhash"]
txno = 0
for tx in block["tx"]:
for vin in tx["vin"]:
if "prevout" in vin:
pvout = vin["prevout"]["scriptPubKey"]
val = vin["prevout"]["value"]
if pvout["type"] == "pubkey":
addr = key_to_addr(pvout["asm"].split(" ")[0])
print("*", addr, tim, count, txno, "spent: pubkey", val)
elif pvout["type"] == "pubkeyhash":
print("*", pvout["address"], tim, count, txno, "spent: address", val)
for vout in tx["vout"]:
val = vout["value"]
pvin = vout["scriptPubKey"]
if pvin["type"] == "pubkey":
addr = key_to_addr(pvin["asm"].split(" ")[0])
print("*", addr, tim, count, txno, "recv: pubkey", val)
elif pvin["type"] == "pubkeyhash":
print("*", pvin["address"], tim, count, txno, "recv: address", val)
txno += 1
count += 1
Note the sctipt runs untill there is no more block hash found in the block processed. It fetches a block, extracts basic time info and then starts going through the structure as we described abouve to get the relevant adresses and event data.
The results
When we start the resulting script, the start of the output is a bit boring.
* 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa 2009-01-03T19:15:05 0 0 recv: pubkey 50.0
* 12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX 2009-01-09T03:54:25 1 0 recv: pubkey 50.0
* 1HLoD9E4SDFFPDiYfNYnkBLQ85Y51J3Zb1 2009-01-09T03:55:44 2 0 recv: pubkey 50.0
* 1FvzCLoTPGANNjWoUo6jUGuAG3wg1w4YjR 2009-01-09T04:02:53 3 0 recv: pubkey 50.0
* 15ubicBBWFnvoZLT7GiU2qxjRaKJPdkDMG 2009-01-09T04:16:28 4 0 recv: pubkey 50.0
* 1JfbZRwdDHKZmuiZgYArJZhcuuzuw2HuMu 2009-01-09T04:23:48 5 0 recv: pubkey 50.0
* 1GkQmKAmHtNfnD3LHhTkewJxKHVSta4m2a 2009-01-09T04:29:49 6 0 recv: pubkey 50.0
* 16LoW7y83wtawMg5XmT4M3Q7EdjjUmenjM 2009-01-09T04:39:29 7 0 recv: pubkey 50.0
* 1J6PYEzr4CUoGbnXrELyHszoTSz3wCsCaj 2009-01-09T04:45:43 8 0 recv: pubkey 50.0
* 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S 2009-01-09T04:54:39 9 0 recv: pubkey 50.0
* 15yN7NPEpu82sHhB6TzCW5z5aXoamiKeGy 2009-01-09T05:05:52 10 0 recv: pubkey 50.0
* 1dyoBoF5vDmPCxwSsUZbbYhA5qjAfBTx9 2009-01-09T05:12:40 11 0 recv: pubkey 50.0
* 1PYELM7jXHy5HhatbXGXfRpGrgMMxmpobu 2009-01-09T05:21:28 12 0 recv: pubkey 50.0
* 17abzUBJr7cnqfnxnmznn8W38s9f9EoXiq 2009-01-09T05:23:40 13 0 recv: pubkey 50.0
* 1DMGtVnRrgZaji7C9noZS3a1QtoaAN2uRG 2009-01-09T05:33:09 14 0 recv: pubkey 50.0
* 1CYG7y3fukVLdobqgUtbknwWKUZ5p1HVmV 2009-01-10T05:45:46 15 0 recv: pubkey 50.0
* 16kktFTqsruEfPPphW4YgjktRF28iT8Dby 2009-01-10T05:45:58 16 0 recv: pubkey 50.0
* 1LPBetDzQ3cYwqQepg4teFwR7FnR1TkMCM 2009-01-10T06:03:11 17 0 recv: pubkey 50.0
* 1DJkjSqW9cX9XWdU71WX3Aw6s6Mk4C3TtN 2009-01-10T06:12:14 18 0 recv: pubkey 50.0
* 1P9VmZogiic8d5ZUVZofrdtzXgtpbG9fop 2009-01-10T06:22:54 19 0 recv: pubkey 50.0
We see that the first few hours there was only mining, no transactions. This is absolutely uninteresting for our kanarie as it are transactions that disclose public keys that could endanger any funds sent to the exposed pubkey after the transaction that emptied the given adress for the first time.
If we look a few years later, things look a bit more interesting.
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 449 spent: address 0.022443
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 449 spent: address 0.0225
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 449 spent: address 7e-05
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 449 spent: address 0.03958
* 19P5Xur6HrDboK2QFYTzz7mk9fCaz89x17 2015-03-20T17:36:11 348439 449 recv: address 0.05
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 449 recv: address 0.034393
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 450 spent: address 7e-05
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 450 spent: address 0.034393
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 450 spent: address 0.01028
* 1NRy9s4vEFSVgNKJAoj3uMg5EjMvJrVBC 2015-03-20T17:36:11 348439 450 spent: address 0.0182
* 19P5Xur6HrDboK2QFYTzz7mk9fCaz89x17 2015-03-20T17:36:11 348439 450 recv: address 0.062743
* 19fYAQMWrTn4wrz621c7XRJmZDUsiPrM2h 2015-03-20T17:36:11 348439 451 spent: address 0.00364538
* 19fYAQMWrTn4wrz621c7XRJmZDUsiPrM2h 2015-03-20T17:36:11 348439 451 spent: address 0.00364517
* 1BdCZJNdAvmZudqsJxP1Uw3UcvFmQFbb9f 2015-03-20T17:36:11 348439 451 recv: address 0.00384187
* 12begeBfqG7Agqk22Wpg4sXomZeXTG463t 2015-03-20T17:36:11 348439 451 recv: address 0.00334868
* 19fYAQMWrTn4wrz621c7XRJmZDUsiPrM2h 2015-03-20T17:36:11 348439 452 spent: address 0.00364622
* 1GS8Wnark2aABZdijp2R7cTJvdErSDksS3 2015-03-20T17:36:11 348439 452 spent: address 0.00379273
* 13TurioKTknCUFXArcQGmHVfVWeqWVsQmN 2015-03-20T17:36:11 348439 452 recv: address 0.00372619
* 1KLiyWmVu3RnsfMCaUvPVGXAJwXoVViUdF 2015-03-20T17:36:11 348439 452 recv: address 0.00361276
I've added a few blank lines for clarity.
Here we see a mix of receive and spent events, all within the exact same block. The script has been running for a number of hours now, and there are 336,081,326 lines of logs like this, and the output is up to april 2015, I'll just leave it running over night, and fix the in-block ordering and deduplication in the code for the next step.
Looking ahead a bit
To know what we are actualy looking for, let's just pick an adress that has shown some re-use in the data so far. Just to get a feel. Here is one:
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2014-01-05T00:44:06 278634 2 recv: address 5.97343541
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2014-01-08T13:29:37 279299 3 recv: address 8.14048721
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2014-01-29T02:00:38 282962 29 spent: address 5.97343541
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2014-01-29T02:00:38 282962 29 recv: address 0.01111491
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2014-02-10T15:36:56 285117 5 spent: address 8.14048721
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2014-02-10T15:36:56 285117 5 recv: address 7.13998721
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2014-02-11T00:28:25 285172 5 recv: address 7.9995
* 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT 2015-04-14T14:17:28 352094 6 recv: address 0.8645
We see that on January 5th 2014, this adress received a little under 6 bitcoin worth less than $6,000 at that time. Then, three days later, the adress receives another 8 bitcoins and a bit. Then, three weeks later the value of the 6 bitcoin USO gets spent, leaving the 8 bitcoin exposed, and in the same block the adress receives about 0.01 bitcoin, or something like $10 worth of bitcoin at that time. This obviously is a wallet that reuses the key for returns. Then 12 days later, the 8 bitcoin gets spent, but 7 bitcoin gets returned to the same old address. Then the next day the address receives another 8 bitcoin. Then 14 months later the same adress reveives a littele under a bitcoin once more.
We can look at the adress 113LjfCRFhbnW2wk9ypcudZC6Fuqi6AftT in an explorer website like blockchain.com, and we see this exposed adress went dormant for nine and a half years before in two days half the remaining balance gets transfered.
Imagine you had a quantum computer capable of what today is still impossible, that is unless you build it in full secrecy for many years and you actualy have one, like Wietse's capturers in Ragnarok Conspiracy. You reversed the public key from the 2014-01-29 or the 2014-02-10 transaction signature, and now you want to steal funds.
This pattern is exactly what you will use to try and stay under the radar. First you transfer a small amount to offset any kanarie bot. The 0.01 BTC would probably be seen as not that relevant, but will reestablish the address as recently used so big transactions after that won't make it to the next check. And if they did, you are not getting too greedy by emptying out the entire funds there. Better yet, you are sticking to the key-reuse pattern and moving back a huge part of the involved USO's to the original address.
Note, I'm in no way suggesting these transactions might be part of an actual quantum blockchain heist, it is way too early in QC development to be viable right now. What I am saying is that the two 2024-11-07 transactions combined should end up in the kanarie statistics that could be analyzed to evaluate the likelyhood of structural quantum computing theft transactions.
For now I'm leaving the script running until it reaches the last block currently on the chain before I can move on the the next step: Using the output of this script to collect all dormant yet vulnerable adresses with unspent outputs. I will choose to let step two run upto one year less than the last current block so we can test our kanarie efforts with on year of blocks that we pretend is a life feed.
I don't know how long untill the first script completes, but when it does I'll be working on part two of this series.
So with Hive said bot cld derive a private key from a public key... what about one's master password.. that's the really important thing assuming most of yr assets are locked (Powered Up/ Saved) and yr checking in a couple of times a week...
Your master password will be relatively and practically safe because derivation uses secure hashing, no public-key cryptography. While quantum computing will weaken secure hash pre-image resistance (Granger) , it does so quadratically against an exponential solution, so SHA256, what is used to derive your keys from your account/role/password combination will remain remain safe for for the meaning of safe most normal people use. Basicly it will take the complete compute power of our planet somewhere around the order of magnitude of ONLY ten or so times the age of the universe to reverse the hash. Maybe I'm one order of magnitude wrong here, it could be 1 or 100 times the age of the universe.
In contrast, attacks against ECDSA (adjusted Shor) once quantum computers are big enough, deriving a private key from a public key, are expected to take just hours up to weeks.
While all or HIVE is vulnerable versus part of bitcoin, Bitcoin is a much bigger target and in this series of posts I hope to show exactly how much of bitcoin is vulnerable.
As for the bot, the bot I want to write isn't doing attacks, it's monitoring bitcoin transaction and creating reports about the types of transactions that might indicate a quantum computing attack on long dormant exposed adresses.
I hope to make a bot that will create either daily or weekly HIVE posts reporting stats on these types of transactions, stats on transactions that might eventualy act like the kanarie in the mine.
It's all a learning process for me right now. It might turn out that the amount of noice on the chain tis too big for usefull reports, but I guess I will find out soon.
It's all fascinating if hard going to make sense of!
some nice work there.. I really admire ur coding skillz. 😉👊
cool! Thanks for ur support. :)
Nice work . Seeing someone proficient in programming language is really a source of motivation to me as I am currently a JavaScript developer (newbie)