Using Bitcoin and Blockchain ideas to Secure our AI Chatbot

Using Bitcoin and Blockchain ideas to Secure our AI Chatbot

If I see another "get rich trading Bitcoin with ChatGPT" YouTube ad or Facebook ad, I will probably murder somebody.

However, the Blockchain had some very interesting ideas we can leverage to our advantage, specifically its worst idea, and its worst idea being of course; "Proof Of Work".

Proof Of Work can actually be used to create an alternative to CAPTCHAs, since at the heart of bots creating garbage traffic is "the economy of scale" - Implying, as long as it's inexpensive to create garbage bot traffic, bots will create garbage traffic.

Proof Of Work

Proof Of Work is arguably the worst idea Satoshi Nakamoto had. It's the reason why buying a gamer graphic card capable of rendering more than 11 frames per second was almost impossible for a decade. Simply because all the Bitcoin miners would buy these cards, throws these into data centres that were consuming "half the world's" electricity, in exchange for a handful of Bitcoins in profit.

However, the reason why it was a bad idea wasn't because it was an actual bad idea, it was simply not used correctly

Proof Of Work can be used in other ways, that all of a sudden makes it a highly useful idea. That other thing is bot protection.

Economy of Scale

If I can have my server invoke your server one million times for the cost of $0.01, I could probably find some ways to profit on it. Such ideas are the foundations for internet bots. As long as bots can create 1 million HTTP invocations for $0.01, they will do it, as long as it is somehow profitable.

If I increase the cost from $0.01 to $10 for these invocations, it becomes much harder to profit. That's a 3 orders of magnitudes increase in cost associated with doing something, which would for all practical concerns eliminate 99% of all bot traffic on the internet.

A Proof Of Work solution therefor has the capacity to eliminate 99% of all bot traffic, while still allowing legitimate users to use your software

Bitcoin mining

Bitcoin mining is based upon Proof of Work (PoW), and its basic idea is as follows.

  1. Start with a seed number of 0
  2. Take some payload, append the seed value to it, and create a hash value
  3. Check if the hash ends with 2 to 5 trailing ZEROS
  4. If the hash ends with enough ZERO values, you've got a valid hash. If not, increment the seed value by one and go back to point 2

Once you've got a valid hash, you have "proof of work".

Creating such hash values requires thousands, and sometimes millions of iterations. Creating one hash is inexpensive, but creating 1,000,000 hash values comes with a real cost. You pay this cost through your electricity bill, because the CPU and GPU you're using to generate such hash values requires a lot of electricity.

Proof Of Work allows you to increase the cost of using your software

Proof of Work as CAPTCHA

CAPTCHA is an acronym and translates to; "Completely Automated Public Turing test to tell Computers and Humans Apart". The purpose of a CAPTCHA is to eliminate bots, and they're typically implemented in all sorts of weird ways.

Example of CAPTCHA

The point of a CAPTCHA is to prove to the computer that you're a human being. The idea is that by proving you're a human being, you've eliminated bots. The problem with CAPTCHAs are that first of all AI is rapidly advancing to the point where it can now actually solve CAPTCHAs - In addition to that you can actually purchase human resources in Bangladesh and other 3rd world countries who's jobs it is to solve CAPTCHAs for clients. A single human being can probably solve roughly 500 CAPTCHAs per hour, implying if you're paying $5 per hour for human labour, you're paying on average $0.01 per CAPTCHA that's solved.

CAPTCHAs are fundamentally broken, and they cannot be fixed!

If you instead use Bitcoin's Proof Of Work idea to generate a token that allows you to invoke some HTTP endpoint, you can now control the amount of work required to invoke your endpoint by configuring your required workload. This allows you to on an individual endpoint level decide what "cost" you want to associate with invoking your endpoint. This cost can range from $0.01 to $10,000 by modifying the endpoint.

What cost you choose, should be proportional to the endpoint's value - Implying a "high value" endpoint could require 4 trailing zeros, while a more commonly accessible endpoint could require only 2 trailing zeros. Since the hash values are shared as hex values, this results in the following math.

  • 1 ZERO requires on average 16 hash values
  • 2 ZEROs requires on average 256 hash values
  • 3 ZEROs requires on average 4,096 hash values
  • 4 ZEROs requires on average 65,536 hash values
  • 5 ZEROs requires on average 1,048,576 hash values

1 zero from above probably has a cost of $0.000001. However, once you reach 5 or more zeros, it would probably require electricity for hundreds of dollars to create a valid hash. For most CAPTCHA solutions, I would expect a value of 2 or 3 would be sufficient. Maybe 4 for high value endpoints, but that's probably more than enough for 99.9999999% of all CAPTCHA solutions on the planet today.

Generating a SHA256 hash value with 3 trailing zeros would probably have an electricity cost of a couple of cents, implying with the example we started out with, having a bot create 1 million HTTP requests, the price would have increased from $0.01 to $40.

No sane bot farm would be willing to pay $40 to generate 1 million HTTP requests unless the target has some extreme high value

And of course, if you're still getting bot traffic, you can easily multiply the cost by 16 once again, resulting in that it would now cost $655 for the same 1 million requests.

Better Usability

The whole point about the above Proof Of Work solution is that it can be automated. This implies you no longer need to bother the user with ridiculous questions such as "find all ducks in the following photo".

When the user is clicking a button on your website, you can start generating a hash value that has a valid timespan of some n seconds. Once a valid hash has been generated, you associate it with your HTTP request, allowing the server to verify the HASH in 0.001 milliseconds, while the client needed to spend 100 milliseconds and $0.01 in electritity to generate the token.

The user doesn't even notice the 0.1 second delay, but the bot will notice it very much. Because now the bot can't invoke your HTTP endpoint more than 10 times per second, instead of invoking it 1,000,000 per second. And if it invokes you 10 times, it'll end up paying a real cost associated with each invocation.

As an added bonus, you now have a CAPTCHA solution that allows for legitimate bot traffic to pass through. This implies you can have the same endpoint for both humans and APIs, ignoring if the caller is a human, as long as it's willing to "put down the work" required to invoke your endpoint.

The task isn't to prevent computers from invoking your endpoint, the task is to prevent malicious computers from invoking your endpoint

Implementation

Generating a valid token is kind of like generating a new "password". This password is only valid for n seconds. In my own code, the default value here is 10 seconds. Implying once a token has been created, it has to be used before 10 seconds have passed. Combined with storing used tokens for 10 seconds on the server to avoid replay attacks, this implies that every single invocation needs a new token. This results in that the client must generate a new token every single time it invokes my server, or the request will be discarded before any of my business logic is executed.

Below is a simple JavaScript file that generates a valid token for my own code.

(function() {

window.mcaptcha = {};

mcaptcha.token = async function(callback, workload = 3) {
  const now = Date.now();
  const toHash = '[[public-key]];' + now;
  let seed = 0;
  const trailing = '0'.repeat(workload);
  while (true) {
    const uIntArray = new TextEncoder('utf-8').encode(toHash + ';' + seed);
    const array = await crypto.subtle.digest('SHA-256', uIntArray);
    const hashArray = Array.from(new Uint8Array(array));
    const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
    if (hashHex.endsWith(trailing)) {
      const token = hashHex + ';' + now + ';' + seed;
      const finished = Date.now();
      callback(token);
      return;
    }
    seed += 1;
  }
}
})();

The above [[public-key]] can just be some random gibberish specific for your server. You can literally exchange it with random characters if you wish, but the server must share this as a public key. The basic idea is that I can use it as follows.

mcaptcha.token(function (token) {
  // Pass token into the server.
}.bind(this), 3);

The token it ends up producing becomes as follows;

876910f4a5e87e23377a83c816db4c700a0fa7f2e482b54fb7fb918765c6a000;1713508963529;2678

The above contains 3 parts, a SHA256 hash, the Unix timestamp in milliseconds, and the seed number it had to use to get 3 trailing zeros in the hash. To write it as pseudo code, consider the following.

hash_sha256('public-key' + ';' + unix_timestamp + ';' + seed)

Then on the server the order of sequence becomes as follows to verify the token.

  1. Verify the token is created in the past but is not older than 10 seconds
  2. Concatenate the public key with the timestamp from the token and the seed value, separated by ';'
  3. Generate a hash from the above result
  4. Verify the hash ends with 3 trailing zeros
  5. Verify the hash matches the hash from the token
  6. Verify the token has not been used previously by checking my cache of previously used tokens

If the above succeeds, I know for a fact that the client has generated a valid token, and in the process on average created 4,096 SHA256 hash values to generate the token, implying the client literally paid $0.01 in electricity to invoke my endpoint.

When I've got a valid token, I can store it in my cache for 10 seconds to avoid replay attacks using the same token again.

For most clients the above can be achieved in some 50 to 300 milliseconds. For a human user this is negligible. But for a bot required to create millions of requests every minute to achieve "economy of scale" it is not negligible.

The above doesn't completely eliminate bots - In fact, I doubt that's even possible. However, a qualified guesstimate would be that it eliminates 99% of all bot traffic, because it's no longer financially justifiable to have bots spending so much electricity to spread their garbage.

You hurt the bot where's it's most painful, in its bank account!

As an additional bonus, it completely eliminates all Postman script kiddies, since the client must be able to execute code to create a valid token. It also eliminates reusing the same token for multiple servers, since each server has their own unique public key - In addition to that you get to market your AI chatbot as "powered by Blockchain technology", and you can actually say that out loud with a straight face, without lying ... 🤪

The AI chatbot powered by Blockchain technology! 😂

Google's reCAPTCHA

It's impossible for me to talk about this without mentioning reCAPTCHA. The reason is because if I use Google's reCAPTCHA version 3 on our AI chatbot, the total download to initialise it becomes 1.7MB. If I use our own Proof Of Work solution the total download becomes 662 bytes. This implies that by creating my own PoW version of reCAPTCHA I have eliminated 99.95% of total bandwidth usage for our AI chatbot related to CAPTCHA, and also made it 2,567 times faster, in addition to making our website probably 1,000% faster.

  • Google reCAPTCHA ~ 1.7MB
  • Our PoW CAPTCHA ~ 662 bytes

Implying Google's reCAPTCHA is 2,567 times heavier on bandwidth usage than my own little PoW CAPTCHA solution. Google's reCAPTCHA violates every single best practice that exists in regards to web development. In fact, when I check our website's speed using PageSpeed Insights, 95% of its negative remarks are about reCAPTCHA.

Google reCAPTCHA is fundamentally broken - To the point where it's not possible to fix!

Simply because if you add reCAPTCHA to your website, you can assume it more than doubles your page load time.

This implies that if you swap out Google reCAPTCHA with the above PoW JavaScript file, the quality of your website has now increased by a lot, and its usability has probably doubled. And the cost for your users is 0.1 additional seconds to invoke your backend.

Conclusion

The above is not for the faint at heart. Even though I've given you all JavaScript required to reproduce it for yourself, you still need to implement it on the server. In addition, it increases the cost of invoking your HTTP endpoints. For secure endpoints, you would still want to use traditional JWT tokens, and not use the above.

This is not an alternative to authentication and authorisation!

However, for publicly available HTTP endpoints, such as those provided by our AI chatbot, available for all without having to authenticate - It's actually quite brilliant. Other places it might be useful is for creating comments and posts on Twitter, Facebook, or YouTube. For normal human users, spending 0.01% of your phone's electricity when adding a comment to a YouTube video is negligible - But for a bot that creates thousands of such comments every minute it is not negligible.

Notice, we will still keep reCAPTCHA as an alternative for users not wanting to use PoW as CAPTCHA. However, by default, PoW will be the default CAPTCHA technology we'll be using for our AI chatbots in the future. Also notice, even though I've published this article, I still haven't created a new release - So our bot is at the moment using the old reCAPTCHA-based technology to prevent bots.

Thomas Hansen

Thomas Hansen I am the CEO and Founder of AINIRO.IO, Ltd. I am a software developer with more than 25 years of experience. I write about Machine Learning, AI, and how to help organizations adopt said technologies. You can follow me on LinkedIn if you want to read more of what I write.

Published 19. Apr 2024