Generating Bitcoin Address from One Word
13 min read
I'm curating this write-up on the fly to demonstrate that I know what I'm talking in relation to my directive to all my subscribers that they should cease using Ledger wallets as soon as possible (as in not one second should be spared in ensuring that one's funds are extracted from one of these devices if possible).
Quick Summary of How This is Going to Go Down
Using nothing more than a couple of rudimentary terminal commands as well as a nifty git repo (html-based page that can be run offline), I'm going to show users how to generate a valid Bitcoin address from scratch using one word (librehash).
Purpose of This Exercise
To make it clear that I have a high level of knowledge, understanding & command of blockchain concepts. This should result in the understanding that users are being warned by a highly credible source.
Setting the Stage
Okay, in order to do this, we need to get organized for a second.
Figuring Out What Type of Address We Want to Create
Ever since BIP11/BIP12 - Bitcoin transactions have been oriented in such a way to where the recipient provides the spending conditions for any Bitcoin that they "receive" (by virtue of their formed address).
In laymen's terms - creating a run-of-the-mill Bitcoin address and passing that to someone for payment is also like you telling that individual:
"Hey, I'd prefer it if you established a condition that says that I must provide some value that, when hashed with sha256 [twice], then hashed with ripemd160, provides the equivalent result. Then, when that's done - I want the protocol to assess my accompanying signature next to that value that I provided (assuming it rendered 'true' for the first half of the script) and determine whether there's a match. If there is - then, and only then, should I be allowed to spend these funds in question)
That's a mouthful - but in everyday language, that just amounts to a generic Bitcoin address.
The blockchain sees it as conditions to spend.
Make sense? Cool.
We Need to Craft a P2SH Transaction
That stands for pay-to-script-hash ; sort of similar to P2PKH, except in this case it allows us to make a payment to a script that could have a wide range of mandated conditions to fulfill before spending (this doesn't have to be related to a private or public key if we don't want it to be).
P2SH was instantiated with BIP16. There was a bit of community in-fighting at the time - but fortunately (for us in this scenario), it made it through.
As noted in the text above, this BIP (among some other augmentations) cemented the principle of shifting redemption considerations to the receiver (vs. being imposed by the sender).
This next bit is extremely important:
Unpacking the Above
"The benefit is allowing a sender to fund any arbitrary transaction, no matter how complicated, using a fixed-length 20-byte hash that is short enough to scan from a QR code or easily copied and pasted."
However, there are certain caveats that must be observed.
"Transactions that redeem these pay-to-script outpoints are only considered standard if the serialized script - also referred to as the redeemScript - is, itself, one of the other standard transaction types."
Our guidance for how we should be manifesting a transaction of this nature can be found in '3' (among the rules for validation of p2sh transactions):
This gives us our next directive for the avenue that we must pursue for my goal of creating a Bitcoin address from the word "librehash"
First Step: Generate the Appropriate Script
We'll keep this simple.
The script in question that we're going to use is:
op_hash160 [librehash hashed] op_equalverify
Explaining the Script That We Just Created
For reference, this page on the Bitcoin Wiki is still the undisputed best location on planet earth to reference op codes on the Bitcoin protocol.
What is 'OP_HASH160'?
This one is simple. OP_HASH160 executes two consecutive hash operations; the first is sha256, followed ripemd160 (to concatenate the SHA256 output).
Why Did We Go With This One?
Because we need to ensure that the final output from all of these operations is truncated down to 20 bits per the specifications outlined in BIP16.
What is 'OP_EQUAL'?
This one is super simple (see below):
Basically, it means that we take the top two items on the stack & assess whether they are equal to one another
All of These Operations Take Place in a Stateless Fashion Facilitated By 'Script'
Look up, 'What is Bitcoin Script language?', and there will be a >90% chance that you'll come across some source on the internet that will describe it as an "archaic", 'forthright' smart contracting language for Bitcoin.
This is...a vague definition.
Simple Way to Think of 'Script' (on Bitcoin)
It is simply a pre-generated execution sequence that runs from start to completion in one loop (before the program is killed; there is no 'loop' in the 'Script' programming language on Bitcoin, so we have an indisputable sense of a "start" and a "finish").
For the Visual Learners Out there
Just kidding (although what's above is entirely accurate).
But here's a more user-friendly gif that shows the simple execution of the 'Script' sequence (this is essentially us looking at the 'cells' through a "microscope" if Bitcoin addresses were people [Biology]):
Necessary Tools For Executing This Endeavor
There are two tools that we're going to use for this.
They are listed below (urls):
We're a little out of order, but we're going to start with the second item on the list here (GCHQ Crypto Chef Tool).
Before we dig into that, let's just check out the second link in question here. (GCHQ CyberChef)
CyberChef Tool Needed to Hash Out Librehash
This would be much more efficient via bash script through the terminal (published below for all those that want that), but since not everyone reading this is a fan of using terminals, let's stick to GUIs, shall we?
Using GCHQ to 'Chef' Up Our Bitcoin Address
Note: this is an open source app
that can be found on GitHub
Upon visiting the site, users should see something similar to the following:
This tool will allow us to transform our quasi-seed phrase into the hashed output that we need for this exercise.
Recalling What We're Looking For Here
op_hash160' output of the word 'librehash' (we're going to ignore base58 encoding or hexadecimal/binary format delineations here for the sake of KISS)
What Does Hash_160 Do Again?
This may seem a bit tricky, but this should be simple to do. So, without further ado - let's get started.
First, we need to hash the word 'librehash' with sha256, then take that output and hash it against sha256 once again - then, finally, hash the second output with ripemd160 to truncate the 32-bit sha256 hash to 20 bytes
Rather than outlining all of this verbosely, I decided to get creative and make a video (also don't want to be too redundant here).
More of a Command Line Junkie?
The script below should render the same result when executed (bash).
#Lets generate a directive on the terminal for the user running the script echo 'Pick a password to hash into your p2sh wallet' #Before we do that we want to make sure that the tty output is silenced so that we dont leave ourselves vulnerable to being compromised by potential bad actors that have managed to get a hold of our bash history somehow stty -echo #this next command takes the outputted response from the stdin and assigns it to the word pass which is important read pass #this next command will pipe the password that we made earlier into openssl which will hash that with sha256 echo $pass | openssl dgst -sha256 >> /tmp/firstrun.txt #we need to rinse repeat again though so lets get to it cat "/tmp/firstrun.txt" | openssl dgst -sha256 >> /tmp/secondrun.txt #great we are almost done we just need to pipe this one more time through openssl but this time for the sake of generating a 20 byte output we will ultimately throw into our script cat "/tmp/secondrun.txt" | openssl dgst -ripemd160 | xclip -sel clip -rmlastnl #for the xclip command users will need to install that via their apt repo if you are using ubuntu or debian cant speak to other distros just check #that command is useful because it pipes the output straight to our clipboard #now its time to clean up bleach='bleachbit -s "/tmp/firstrun.txt" > "/dev/null" 2>&1' #the command above uses the bleachbit tool to scrub the first outputted file from our tmpfs even though it should be wiped after we end our session anyway since tmpfs is RAM #the above command only gave a directive to execute when the newly created bleach variable is called in our subshell so lets try it out eval "$bleach" #we could be super careful and create a conditional script that only proceeds if the tmp directory is devoid of the file in question that we just attempted to bleach out of existence with the above command but I dont think that we need to go that overkill with this bleachblonde='bleachbit -s "/tmp/secondrun.txt" > "/dev/null" 2>&1' #this is a bit redundant but I ran the commands separately for the sake of being demonstrative here eval "$bleachblonde" #we need to turn the terminal on stty echo # now we are done exit
Super Important Note Here
When running this script, I noticed that I was getting a different output than the GCHQ tool.
This confused the living hell out of me (because this should not happen under any circumstance since all hash signatures being used here are deterministic)
The Culprit = prefixed '(stdin)' tag that openssl outputs:
The best immediate solution though would be to merely output the last 64 characters (32 bytes) of the file where we piped the hashed result into. Problem solved.
Shouldn't Screw Anyone Though
If one were to consistently use that bash script (and only that), then the output would deterministically be the same (we're dipping into stateless address generation / BIP38 territory here); but its best to do things in the most consistent, interoperable way possible (as recommended by the IETF in numerous RFCs).
With all of that being said...We did it!
No time to pat ourselves on the back though - there's still more work that must be done.
Generating Our Transaction Conditions
For clarity's sake, the rendered output that we're going to go with is the one that was produced by the GCHQ tool (since this is the easiest to replicate for everyone following alone).
For the record the output (20-byte) should be:
Visiting the 'Bitcoin Forge' Toolkit
I'm not entirely sure who the fellas are behind this tool (the first link that I published above), but it is absolutely magnificent.
Users can essentially craft any transaction of their choosing with a high level of granularity in their preference decision (down to the deliberate crafting of a wallet address via preferred transaction type if that's meaningful)
"Okay, what's the website dude?"
URL = https://improvein.github.io/bitcoin-forge/#/
This site is 1 million percent safe and so is the code ; you can audit it for yourselves here: https://github.com/improvein/bitcoin-forge
my gripe here though is that the calls are made internally into the file system & also there are no integrity hashes accompanying the loaded .js, which we definitely want to include for an app of such a sensitive nature like this one; these are ultimately trivial additions to add in, in the grand scheme though (yeah, I forked the codebase, hacked around and attached argon2-driven stateless bitcoin address generation leveraging the same 'memwallet' specs 'Keybase.io' did in their Bitcoin live example)
Enough of me rambling though - let's get to it.
Generating Our Conditions
Once you get to that site (or spin it up yourself locally), you're going to want to click here:
That will take us here:
No need for intimidation, this is the easy part.
One only needs to follow through as such:
if you're curious how this was done manually, there will be another video showing the entire process from this point
Now We Serialize the Script
By clicking the 'compile' button:
This leaves us with:
Doubling Back to Amend One Minor Detail
I got ahead of myself here because what we've provided above would serve as the second part of the 'Script' scheme (partitioned by the CODESEPARATOR op_code).
What I just wrote in the paragraph above probably makes zero sense and I'll accept that for now because it's outside of the scope of this write-up.
More to the Point
Given the nature of hashing and cryptography in general, we can "shortcut" some of the address generation process - which will allow us to 'game' the 'Script' into thinking that the script hash is a legitimate hash of something that derived from ecdsa (secp256k1 ; elliptic curve transformed).
'How does one do this?'
By running an elliptic curve operation on any 32-bit (or '33-bit') input, compressing the result (32 bits) before appending the mainnet version bytes to the result.
Simply concatenating ('XOR', perhaps?) two 32-bit strengths, yielding a 64-bit output, then appending '04' (version bytes/flag; can't remember which) to the beginning
quick reminder, make sure that the output is in hexadecimal format
After curating the bash script (and including videos, media, etc.), I realized my folly in adding an erroneous additional sha256 hash operation (there's only one, which must be either derived from a versioned Bitcoin).
So let's step back a minute and see if we can't get ourselves a P2SH address from our original input ('librehash') using a more creative means of deriving said address.
In the photo above, I hashed 'librehash' with 'SHAKE256' (part of the SHA-3 family).
SHAKE is an XOF hash function (variable output hash). This means that it can take a hash of virtually any given length & churn out a uniform output.
Ironically, this is a pain point for SHA-2 (i.e., it doesn't 'pad' inputs well). I imagine that Satoshi knew this, which would explain the insistence on 32-bit architecture & 32-bit / 33-bit inputs (along w base58 encoding) to mitigate padding attacks on transactions that would allow one to actually forge the sender's signature (Satoshi knew his shit).
Enough rambling - let's append a mainnet version code to our 64-digit output ('04').
The above would be considered an 'uncompressed' Bitcoin address.
Or, we could try our luck by piping the unaugmented SHAKE256 output of 'librehash' into 'shake128' to render a 32-bit hashed output.
We can append '02' (signaling a valid compressed ecdsa public key in hexadecimal format on the Bitcoin protocol).
Then run that output through op_256 (hash256 twice) ; yes , tedious but takes no time in terms of computing cycles.
But we don't even need to do that because we can simply hash our foe-compressed key with ripemd160 and generate a valid address from there.
(remember, p2sh mandates a 20-bit input)
(One ripemd160 operation later)
Renders the following output:
Which we then pipe into a slightly different Bitcoin script:
The 'compile' button serializes it for us:
There are plenty of tools in the world that will allow us to transform our valid redemption script into an actual Bitcoin address (like the one I showed you).
Every single operation users were shown in this write-up was performed offline
This entire write-up sets up a meaningful convo that I would like to have about why wallet providers insist on forcing users to generate addresses via mnemonics (which is literally a human-readable, easy-to-remember private key, that users don't even need to interact with)