I'm curating this write-up on the fly to demonstrate that I know what I'm talking in relation to my directive to anyone that reads / follows / checks out content published by Librehash that they should cease using Ledger wallets as soon as possible.
They are poorly constructed, insecure nonsensical physical devices that provide nowhere near the type of cryptographic protection that an actual HSM device does.
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 ran 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. Which 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 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 that this BIP grants us is significant.
"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 structure of the script we're going to use will look a bit similar to the following:
op_hash160 [librehash hashed] op_equal
think the op code should be in all caps here, can't remember - you get it though
Breaking Down the Mock Script
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 = https://en.bitcoin.it/wiki/Script
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 a 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/human-friendly .gif that provides a really good visual of the execution process (remember, this must be deterministic, - i.e., ensure that we get the same output for the same input [true / false] (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 going to go a little out of order here, but let's going to start with the second item on the list here (GCHQ Crypto Chef Tool).
Don't worry, we're going to take a field trip before we start quizzing students
CyberChef Crypto Web App Tour
That's 'crypto' as in cryptography
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 the Cryptographic Primitives Needed For Our Bitcoin Address
The URL (again) for the GCHQ CryptoChef Web App = https://gchq.github.io/CyberChef/
this is an open source app; code is publicly published 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
The 'op_hash160' output of the word 'librehash' (we're going to ignore base58 encoding / checksums / hexadecimal / binary format delineations or explanations here for the purposes of not overloading anyone here)
What Does Hash_160 Do Again?
op_hash160 = OP_HASH160 = ripemd160 of SHA256(sha256(x))
This is actually not true! I added in another hash operation here for some reason ; oh well, no harm / no foul - if this is really an issue to someone, would recommend that they simply append the op code, op_sha256 to the beginning of the script and, voila
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' using sha256, then pipe the output and hash it against sha256 once again, before finally hashing that output using ripemd160 to truncate the 32/33-bit sha256 hash output 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).
Take a look!
More of a Command Line Junkie?
The script below should render the same result when executed (bash).
Sorry this isn't true, forgot to trim the '(stdin)' descriptor that gets appended as a prefix to hash operations generated with openssl directly
#!/bin/sh #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
If you're running this via terminal, then this may be a pain in the ass for you if your OS isn't Debian / Ubuntu, because there are three programs that must be downloaded in order for this script to successfully execute - and none of those packages are included within the package repo of other popular Linux distros (i.e., 'Fedora', 'Arch Linux', and others).
Small List of Tools to Download
#!/bin/bash # Better off running: sudo apt -y install xclip argon2 bleachbit # Run a quick update afterward sudo apt update # If your terminal session is stale (i.e., not allowing you to call these commands directly from the command line via name), then execute this code below: exec bash # ^^ That's it! You'll be able to remain logged into the same terminal / TTY w/o having to log out & back in, swap users, etc.
To the point above re: '(stdin)' tags appended as a prefix by openssl
I'm sure there's some easy way to 'silence' / 'quiet' that for those familiar with API if someone really wanted to.
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.
Most important thing (in the immediate future) is that the outputs are deterministic.
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 (on down to the deliberate crafting of a wallet address via preferred transaction type if that's meaningful)
"Okay, what's the website dude?"
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 TX Spending 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 need 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:
Which leaves us with this:
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 its 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 somethign 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
brief 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').
Above would be considered an 'uncompressed' Bitcoin address (hexadecimal format).
Or, we could try our luck by piping the un-augmented SHAKE256 output of 'librehash' into 'SHAKE128', yielding a 32-bit hashed output.
When finished, we need to append '02' in the front (signaling a valid compressed ecdsa public key [not] 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 will only take a 'script hash' that's 20 bits or less)
(ripemd160 operation later)
Renders the Following Output:
Which we then pipe into a slightly different Bitcoin script (than what we first had in mind):
The 'compile' button serializes it for us:
There are plenty of tools in the world that will allow us to transform our valid redeem script (started with 'a') into an actual P2SH Bitcoin address (like the one I showed you).
That address is 3GSHqv6FPk2YqGddBXjW2gVBUrs7KeUdrv
Maybe I'll send some funds to it to force the true go getters & money-holics out there to get their hands dirty with this blockchain stuff (fuck DeFi).
Every single operation 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, which users don't even need to interact with)