crypto-lite - Cryptographic Secure Hash Functions and Public Key Signature Algorithms Made Easy
Usage
Secure Hashing / Hash Functions
SHA256 - Secure Hash Algorithm (SHA) 256-Bit (32 Bytes)
Note: By default all hash functions return binary strings.
Use String#hexdigest
(or String#bin_to_hex
or String#btoh
)
to convert binary strings to hex(adecimal)
strings (via Bytes.bin_to_hex
).
require 'crypto'
sha256( "abc" ).hexdigest
sha256( "abc".b ).hexdigest
sha256( "\x61\x62\x63" ).hexdigest
sha256( hex: '616263' ).hexdigest
sha256( hex: '0x616263' ).hexdigest
sha256( hex: '0X616263' ).hexdigest
Bonus Back Stage Tip: How does SHA256 work?
Try this amazing animation of the SHA256 hash function in your very own terminal by Greg Walker.
More of a code golfer? See ½ Kilo of SHA256 by Jan Lelis - yes, the SHA256 algorithm coded (from scratch) in 500 bytes of ruby.
Onwards with more sha256 examples:
sha256( "a" ).hexdigest
sha256( "\x61" ).hexdigest
sha256( hex: '61' ).hexdigest
sha256( hex: '0x61' ).hexdigest
sha256( "Hello, Cryptos!" ).hexdigest
SHA3-256 - Secure Hashing Algorthim (SHA) 3, 256-Bit (32 Bytes)
sha3_256( "Hello, Cryptos!" ).hexdigest
Note: Yes, SHA256 vs SHA3-256 / SHA-2 vs SHA-3 the hashing functions are
different (although the 256-bit hash size output is the same).
The sha256 hashing function is part of the Secure Hash Algorithm (SHA) 2 family / standards first published in 2001.
The sha3_256 is part of the (newer) Secure Hash Algorithm (SHA) 3 family / standards first published in 2015
(and uses the Keccak cryptographic primitive "under the hood").
Keccak 256-Bit
keccak256( "Hello, Cryptos!" ).hexdigest
Aside - Keccak vs SHA3 / Original vs Official
In 2004 the U.S. National Institute of Standards and Technology (NIST)
changed the padding to SHA3-256(M) = KECCAK [512] (M || 01, 256)
.
This is different from the padding proposed by the Keccak team in
the original Keccak SHA-3 submission version 3 (the final, winning version).
The difference is the additional '01'
bits appended to the message.
To help avoid confusion the "submitted original version 3" SHA-3 Keccak
hashing is now called "Keccak"
and the finalized NIST SHA-3 standard "SHA3".
Tip: If you don't know what variant of the hash function you have -
original or official? - check your hash:
For keccak 256-bit:
keccak256( '' ).hexdigest
For sha3 256-bit:
sha3_256( '' ).hexdigest
RMD / RIPE-MD - RACE¹ Integrity Primitives Evaluation Message Digest 160-Bit
¹: Research and development in Advanced Communications technologies in Europe
rmd160( "Hello, Cryptos!" ).hexdigest
ripemd160( "Hello, Cryptos!" ).hexdigest
Aside - Hex String "0x616263"
vs Binary String "\x61\x62\x63" == "abc"
Note: All hash functions operate on binary strings ("byte arrays")
and NOT hex strings.
Note: For hex strings the 0x
or 0X
prefix is optional.
Examples of hex strings:
"61" "\x61" == "a"
"0x61" "\x61" == "a"
"616263" "\x61\x62\x63" == "abc"
"0x616263" "\x61\x62\x63" == "abc"
"0X616263" "\x61\x62\x63" == "abc"
"93ce48570b55c42c2af816aeaba06cfee1224fae"
"0x93ce48570b55c42c2af816aeaba06cfee1224fae"
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
"0xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
You can use [str].pack( 'H*' )
to convert a hex string into a binary string.
Note: The standard Array#pack
conversion
will NOT "auto-magically" cut-off the 0x
or 0X
prefix.
If you know you have a hex string use the hex:
keyword to pass
in the arg(ument)
to the hash function and that will "automagically"
handle the hex-to-bin conversion for you. Example:
sha256( hex: '61' ).hexdigest
sha256( hex: '0x61' ).hexdigest
sha256( hex: '616263' ).hexdigest
sha256( hex: '0x616263' ).hexdigest
sha256( hex: '0X616263' ).hexdigest
Hash Function Helpers
HASH160 - RMD160(SHA256(X))
All-in-one "best-of-both-worlds" helper - first hash with sha256 and than hash with rmd160. Why? Get the higher security of sha256 and the smaller size of rmd160.
hash160( '02b9d1cc0b793b03b9f64d022e9c67d5f32670b03f636abf0b3147b34123d13990' ).hexdigest
hash160( '02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737' ).hexdigest
HASH256 - SHA256(SHA256(X))
All-in-one double sha256 hash helper, that is, first hash with sha256 and than hash with sha256 again. Why? Arguably higher security.
SHA256(SHA256(X)) was proposed by Ferguson and Schneier in their excellent book "Practical Cryptography"
(later updated by Ferguson, Schneier, and Kohno and renamed "Cryptography Engineering") as a way to make SHA256 invulnerable
to "length-extension" attack. They called it "SHA256D".
hash256( '6fe6b145a3908a4d6616b13c1109717add8672c900' ).hexdigest
Base58 Encoding / Decoding Helpers
BASE58
Base58 encoding / decoding with leading zero bytes (in hex or binary strings) getting encoded from 00
to 1
and back:
base58( hex: "516b6fcd0f" )
base58( hex: "00000000000000000000123456789abcdef0" )
base58( hex: "0x516b6fcd0f" )
base58( hex: "0x00000000000000000000123456789abcdef0" )
unbase58( "ABnLTmg" )
unbase58( "111111111143c9JGph3DZ" )
BASE58CHECK - BASE58(X || SHA256(SHA256(X))[:4])
Base58 encoding with an extra 4-byte secure hash checksum.
base58check( hex: "516b6fcd0f" )
base58check( hex: "00f54a5851e9372b87810a8e60cdd2e7cfd80b6e31" )
unbase58check( "237LSrY9NUUas" )
unbase58check( "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs" )
Public Key Signature Algorithms
Elliptic Curve Digital Signature Algorithm (ECDSA)
Private Key
An ECDSA (Elliptic Curve Digital Signature Algorithm) private key is a random number between 1 and the order of the elliptic curve group.
private_key = EC::PrivateKey.generate
private_key.to_i
private_key.to_s
Derive / (Auto-)Calculate the Public Key - Enter Elliptic Curve (EC) Cryptography
The public key (K
) are two numbers (that is, a point with the coordinates x and y) computed by multiplying
the generator point (G
) of the curve with the private key (k
) e.g. K=k*G
.
This is equivalent to adding the generator to itself k
times.
Magic?
Let's try:
private_key = EC::PrivateKey.new( 1234 )
public_key = private_key.public_key
point = public_key.point
point.x
point.y
point.x.to_s(16)
point.y.to_s(16)
Sign a transaction with an (elliptic curve) private key:
tx = 'from: Alice to: Bob cryptos: 43_000_000_000'
txhash = sha256( tx ).hexdigest
private_key = EC::PrivateKey.new( 1234 )
signature = private_key.sign( txhash )
signature = EC.sign( txhash, private_key )
signature.r
signature.s
signature.r.to_s(16)
signature.s.to_s(16)
Verify a signed transaction with an (elliptic curve) public key:
tx = 'from: Alice to: Bob cryptos: 43_000_000_000'
txhash = sha256( tx ).hexdigest
public_key = EC::PublicKey.new(
102884003323827292915668239759940053105992008087520207150474896054185180420338,
49384988101491619794462775601349526588349137780292274540231125201115197157452
)
signature = EC::Signature.new(
80563021554295584320113598933963644829902821722081604563031030942154621916407,
58316177618967642068351252425530175807242657664855230973164972803783751708604
)
public_key.verify?( txhash, signature )
EC.verify?( txhash, signature, public_key )
public_key = EC::PublicKey.new(
0xe37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2,
0x6d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c
)
signature = EC::Signature.new(
0x3306a2f81ad2b2f62ebe0faec129545bc772babe1ca5e70f6e56556b406464c0,
0x4fe202bb0835758f514cd4a0787986f8f6bf303df629dc98c5b1a438a426f49a
)
public_key.verify?( txhash, signature )
EC.verify?( txhash, signature, public_key )
To sum up:
- The (raw) private key is a 256-bit unsigned integer number
- The (raw) public key is a point (x,y), that is, two 256-bit unsigned integer numbers - derived (calculated) from the private key
- A (raw) signature is composed of (r,s), that is, two 256-bit unsigned integer numbers
That's all the magic.
Real-World Examples / Cookbook
Bitcoin Chains
Dodge "Shiba Inu" Chains
Litecoin Chains
Ethereum Chains
Bitcoin (BTC), Bitcoin Cash (BCH), Bitcoin Cash Satoshi Vision (BSV), Bitcoin Cash ABC (BCHA)
Bitcon Public Service Announcement:
Bitcoin number go up because more people want bitcoin. Bitcoin becomes more and more valuable.
- 1,000 HODLers
- 10,000 HODLers
- 100,000 HODLers
- 1,000,000 HODLers
- 10,000,000 HODLers
- 100,000,000 HODLers
- 1,000,000,000 HODLers
- 10,000,000,000 HODLers
- 100,000,000,000 HODLers and on and on
People will come to understand bitcon.
-- Dan McArdle, Bitcoin "There is No Alternative", Bitcoin is the New (Gold) Standard
BEWARE: Yes, Bitcoin Is a Ponzi - Learn How the Investment Fraud Works »
Derive the Bitcoin (Elliptic Curve) Public Key from the Private Key
A private key in bitcoin is a 32-byte (256-bit) unsigned / positive integer number.
Or more precise the private key is a random number between 1
and the order of the elliptic curve secp256k1.
EC::SECP256K1.order
EC::SECP256K1.order.to_s(16)
Step 1 - Let's generate a private key
private_key = EC::PrivateKey.generate
private_key.to_i
private_key.to_s
private_key = EC::PrivateKey.generate
private_key.to_i
private_key.to_s
Or use your own (secure) random generator.
Trivia Note: The smallest possible (BUT HIGHLY UNSECURE)
private key is 1 (not 0).
def generate_key
1 + SecureRandom.random_number( EC::SECP256K1.order - 1 )
end
generate_key
generate_key
Aside: What's Base 6? Let's Roll the Dice
An important part of creating a private key is ensuring the random number
is truly random.
Physical randomness is better than computer generated pseudo-randomness.
The easiest way to generate physical randomness is with a dice.
To create a private key you only need one six-sided die
which you roll ninety nine times.
Stopping each time to record the value of the die.
When recording the values follow these rules: 1=1, 2=2, 3=3, 4=4, 5=5, 6=0.
By doing this you are recording the big random number, your private key,
in base 6 format.
def roll_dice
SecureRandom.random_number( 6 )
end
priv_base6 = 99.times.reduce('') { |buf,_| buf << roll_dice.to_s }
Exercise:
Turn the ninety nine character base 6 private key into a base 10 or base 16 number.
priv = priv_base6.to_i(6)
priv.to_s(16)
Aside: What's Base 2? Let's Flip A Coin - Heads or Tails?
Triva Quiz: For an (unsigned) 256-bit number - how many times
do you need to flip the coin?
Step 2 - Let's derive / calculate the public key from the private key - Enter elliptic curve (EC) cryptography
The public key (K
) are two numbers (that is, a point with the coordinates x and y) computed by multiplying
the generator point (G
) of the curve with the private key (k
) e.g. K=k*G
.
This is equivalent to adding the generator to itself k
times.
Magic?
Let's try:
private_key = EC::PrivateKey.new( 50303382071965675924643368363408442017264130870580001935435312336103014915707 )
public_key = private_key.public_key
point = public_key.point
point.x
point.y
and convert the point to the compressed or uncompressed
Standards for Efficient Cryptography (SEC)
format used in Bitcoin:
point.to_s( :compressed )
point.to_s( :uncompressed )
References
Generate the Bitcoin (Base58) Address from the (Elliptic Curve) Public Key
Let's follow the steps from How to create Bitcoin Address:
pk = "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
step1 = sha256( hex: pk ).hexdigest
step2 = ripemd160( hex: step1 ).hexdigest
step3 = "00" + step2
step4 = sha256( hex: step3 ).hexdigest
step5 = sha256( hex: step4 ).hexdigest
step6 = step5[0..7]
step7 = step3 + step6
addr = base58( hex: step7 )
Or let's try again with the shortcut helpers:
HASH160 - RMD160(SHA256(X))
BASE58CHECK - BASE58(X || SHA256(SHA256(X))[:4])
pk = "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
step1 = hash160( hex: pk ).hexdigest
step2 = "00" + step1
addr = base58check( hex: step2 )
References
Encode the Bitcoin Private Key in the Wallet Import Format (WIF)
A Wallet Import Format (WIF) private key is a standard private key, but with a few added extras:
- Version Byte prefix - The network the private key is to be used on.
0x80
= Mainnet
0xEF
= Testnet
- Compression Byte suffix (optional) - Flag if the private key is used to create a compressed public key.
- Checksum - Useful for detecting errors/typos when you type out your private key; calculated using the first 4 bytes of the double sha256 hash
SHA256(SHA256(X))[:4]
.
This is all then converted to Base58, which shortens the string and makes it easier to transcribe.
privatekey = "ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2"
extended = "80" + privatekey + "01"
checksum = hash256( hex: extended ).hexdigest[0..7]
extendedchecksum = extended + checksum
wif = base58( hex: extendedchecksum )
Or let's try again with the base58check (BASE58(X || SHA256(SHA256(X))[:4])
) shortcut helper:
privatekey = "ef235aacf90d9f4aadd8c92e4b2562e1d9eb97f0df9ba3b508258739cb013db2"
extended = "80" + privatekey + "01"
wif = base58check( hex: extended )
References
Bonus: Bitcon Tip - How to Buy Bitcoin (The CO₂-Friendly Way)
- Take one $50 bill, five $10 bills, or ten $5 bills (I wouldn't recommend change - stay with paper money).
- Go to the bathroom.
- Lift the lid of the loo.
- Throw money in.
- Flush down water.
Congrats! You just purchased $50 worth of Bitcoin - without fucking the planet!
-- Trolly McTrollface, Bitcon Greater Fool Court Jester
Read more Crypto Quotes »
Dodge
Even fun money is money, and a toy cryptocurrency can be turned into real money;
the supply of gullibility is deep, if not infinite.
So the shibes started dreaming of getting rich for free...
-- David Gerard, Confused About Dogecoin? Here's How It (Doesn't) Work
Dogecoin is the people's crypto.
The future currency of earth and mars. Much wow!
-- Elon Musk, February 2021
Derive the Dodge (Elliptic Curve) Public Key from the Private Key
Short version: Same as in Ethereum, Bitcoin, Litecoin
Long version:
A private key in dodge is a 32-byte (256-bit) unsigned / positive integer number.
Or more precise the private key is a random number between 1
and the order of the elliptic curve secp256k1.
EC::SECP256K1.order
EC::SECP256K1.order.to_s(16)
Step 1 - Let's generate a private key
private_key = EC::PrivateKey.generate
private_key.to_i
private_key.to_s
private_key = EC::PrivateKey.generate
private_key.to_i
private_key.to_s
Step 2 - Let's derive / calculate the public key from the private key - Enter elliptic curve (EC) cryptography
The public key (K
) are two numbers (that is, a point with the coordinates x and y) computed by multiplying
the generator point (G
) of the curve with the private key (k
) e.g. K=k*G
.
This is equivalent to adding the generator to itself k
times.
Magic?
Let's try:
private_key = EC::PrivateKey.new( 50303382071965675924643368363408442017264130870580001935435312336103014915707 )
public_key = private_key.public_key
point = public_key.point
point.x
point.y
and convert the point to the compressed or uncompressed
Standards for Efficient Cryptography (SEC)
format used in Dodge:
point.to_s( :compressed )
point.to_s( :uncompressed )
Generate the Dodge Address from the (Elliptic Curve) Public Key
Short version:
Same as bitcoin or litecoin.
Only difference - Add the version byte 0x1e
prefix for Dodge Main Network - P2PKH (pay to public key hash).
Long version:
Let's use the shortcut hash function helpers:
HASH160 - RMD160(SHA256(X))
BASE58CHECK - BASE58(X || SHA256(SHA256(X))[:4])
pk = "022744c02580b4905349bc481a60c308c2d98d823d44888835047f6bc5c38c4e8f"
step1 = hash160( hex: pk ).hexdigest
step2 = "1e" + step1
addr = base58check( hex: step2 )
Litecoin
Derive the Litecoin (Elliptic Curve) Public Key from the Private Key
Short version: Same as in Ethereum, Bitcoin, Dodge.
Generate the Litecoin Address from the (Elliptic Curve) Public Key
Short version: Same as in Bitcoin or Dodge.
Only difference - Add the version byte 0x30
prefix for Litecoin Main Network - P2PKH (pay to public key hash).
Ethereum
Derive the Ethereum (Elliptic Curve) Public Key from the Private Key
A private key in ethereum is a 32-byte (256-bit) unsigned / positive integer number.
Or more precise the private key is a random number between 1
and the order of the elliptic curve secp256k1.
EC::SECP256K1.order
EC::SECP256K1.order.to_s(16)
Note: A "raw" private key in ethereum is the same as in bitcoin, litecoin, dodge & co using the same elliptic curve secp256k1.
See Derive the Bitcoin (Elliptic Curve) Public Key from the Private Key above.
Step 1 - Let's generate a private key
private_key = EC::PrivateKey.generate
private_key.to_i
private_key.to_s
private_key = EC::PrivateKey.generate
private_key.to_i
private_key.to_s
Or use your own (secure) random number.
Let's follow along the example
in the Mastering Ethereum book and let's use the random number:
0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315
.
private_key = EC::PrivateKey.new( 0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315 )
private_key.to_i
private_key.to_s
Step 2 - Let's derive / calculate the public key from the private key - Enter elliptic curve (EC) cryptography
The public key (K
) are two numbers (that is, a point with the coordinates x and y) computed by multiplying
the generator point (G
) of the curve with the private key (k
) e.g. K=k*G
.
This is equivalent to adding the generator to itself k
times.
Magic?
Let's try:
private_key = EC::PrivateKey.new( 0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315 )
public_key = private_key.public_key
point = public_key.point
point.x
point.y
point.x.to_s(16)
point.y.to_s(16)
and convert the point to the raw uncompressed
format used in Ethereum:
"%64x%64x" % [point.x, point.y]
("%64x" % point.x) + ("%64x" % point.y)
References
Generate the Ethereum Address from the (Elliptic Curve) Public Key
Let's again follow along the example
in the Mastering Ethereum book and let's (re)use the public key (from above):
Step 1: Use the keccak256 hashing function
to calculate the hash of the public key
pub = "6e145ccef1033dea239875dd00dfb4fee6e3348b84985c92f103444683bae07b83b5c38e5e2b0c8529d7fa3f64d46daa1ece2d9ac14cab9477d042c84c32ccd0"
hash = keccak256( hex: pub ).hexdigest
Step 2: Keep only the last 20 bytes (least significant bytes), this is the ethereum address
hash[24,40]
hash[-40..-1]
hash[-40,40]
Note: Most often you will see ethereum addresses with the prefix 0x
that indicates
they are hexadecimal-encoded, like this: 0x001d3f1ef827552ae1114027bd3ecf1f086ba0f9
.
References
Install
Just install the gem:
$ gem install crypto-lite
License
The scripts are dedicated to the public domain.
Use it as you please with no restrictions whatsoever.
Send them along to the wwwmake forum.
Thanks!