elliptic - elliptic curve digital signature algorithm (ECDSA) cryptography with OpenSSL made easy (incl. secp256k1 curve)
- home :: github.com/rubycocos/blockchain
- bugs :: github.com/rubycocos/blockchain/issues
- gem :: rubygems.org/gems/elliptic
- rdoc :: rubydoc.info/gems/elliptic
Did you know? All you need to open up a new account on a blockchain is an (unsigned) 256-bit / 32-byte integer number. Yes, that's it. No questions asked. The private key is the secret "magic" that unlocks your own bank.
Q: What's the maximum value for a 256-bit / 32-byte integer number (hint 2^256-1)?
Maximum value of 2^256-1 =
2**256-1
#=> 115792089237316195423570985008687907853269984665640564039457584007913129639935
(2**256-1).digits.size # or to_s.length
#=> 78
Yes, that's 78 (!) decimal digits.
Let's (re)try the maximum value for a 256-bit (32-byte) integer number in hexadecimal (base 16) and binary (base 2) format?
(2**256-1).to_s(16)
#=> "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
(2**256-1).digits(16).size # or to_s(16).length
#=> 64
(2**256-1).to_s(2)
#=> "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
(2**256-1).digits(2).size # or to_s(2).length
#=> 256
Surprise - a 256-bit number has 256 binary digits (0 and 1s).
BEWARE - Blockchain Bandits! If you use a low integer number e.g. 1, 2, etc. your account is guaranteed to get robbed by blockchain bandits in seconds.
(See A "Blockchain Bandit" Is Guessing Private Keys and Scoring Millions by Andy Greenberg, Wired Magazine, April 2019)
An ECDSA (Elliptic Curve Digital Signature Algorithm) private key is a random number between 1 and the order of the elliptic curve group.
require 'elliptic'
# Auto-generate (random) private key
private_key = EC::PrivateKey.generate # by default uses Secp256k1 curve (used in Bitcoin and Ethereum)
private_key.to_i
#=> 29170346885894798724849267297784761178669026868482995474159965944722616190552
private_key.to_s
#=> "407dd4ccde53d30f3a9cda74ceccb247f3997466964786b59e4d68e93e8f8658"
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:
# This private key is just an example. It should be much more secure!
private_key = EC::PrivateKey.new( 1234 ) # by default uses Secp256k1 curve (used in Bitcoin and Ethereum)
public_key = private_key.public_key ## the "magic" one-way K=k*G curve multiplication (K=public key,k=private key, G=generator point)
point = public_key.point
point.x
#=> 102884003323827292915668239759940053105992008087520207150474896054185180420338
point.y
#=> 49384988101491619794462775601349526588349137780292274540231125201115197157452
point.x.to_s(16)
#=> "e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2"
point.y.to_s(16)
#=> "6d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c"
Sign a transaction with an (elliptic curve) private key:
# Step 1 - Calculate the Transaction (tx) Hash
tx = 'from: Alice to: Bob cryptos: 43_000_000_000'
txhash = Digest::SHA256.digest( tx )
# Step 2 - Get the Signer's Private key
private_key = EC::PrivateKey.new( 1234 ) # This private key is just an example. It should be much more secure!
# Sign!
signature = private_key.sign( txhash )
# -or-
signature = EC.sign( txhash, private_key )
signature.r
#=> 80563021554295584320113598933963644829902821722081604563031030942154621916407
signature.s
#=> 58316177618967642068351252425530175807242657664855230973164972803783751708604
signature.r.to_s(16)
#=> "3306a2f81ad2b2f62ebe0faec129545bc772babe1ca5e70f6e56556b406464c0"
signature.s.to_s(16)
#=> "4fe202bb0835758f514cd4a0787986f8f6bf303df629dc98c5b1a438a426f49a"
Verify a signed transaction with an (elliptic curve) public key:
# Step 1 - Calculate the Transaction (tx) Hash
tx = 'from: Alice to: Bob cryptos: 43_000_000_000'
txhash = Digest::SHA256.digest( tx )
# Step 2 - Get the Signer's Public Key
public_key = EC::PublicKey.new(
102884003323827292915668239759940053105992008087520207150474896054185180420338,
49384988101491619794462775601349526588349137780292274540231125201115197157452
)
# Step 3 - Get the Transaction's Signature
signature = EC::Signature.new(
80563021554295584320113598933963644829902821722081604563031030942154621916407,
58316177618967642068351252425530175807242657664855230973164972803783751708604
)
# Don't Trust - Verify
public_key.verify?( txhash, signature )
# -or-
EC.verify?( txhash, signature, public_key )
#=> true
# or using hexadecimal numbers
public_key = EC::PublicKey.new(
0xe37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2,
0x6d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c
)
signature = EC::Signature.new(
0x3306a2f81ad2b2f62ebe0faec129545bc772babe1ca5e70f6e56556b406464c0,
0x4fe202bb0835758f514cd4a0787986f8f6bf303df629dc98c5b1a438a426f49a
)
public_key.verify?( txhash, signature )
# -or-
EC.verify?( txhash, signature, public_key )
#=> true
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.
To get the all-in-one-string public key from a point with the coordinates x and y use the Standards for Efficient Cryptography (SEC) 1) uncompressed format or the 2) compressed format:
# 1) Uncompressed format (with prefix 04)
# Convert to 64 hexstring characters (32 bytes) in length
prefix = '04'
pubkey = prefix + "%064x" % point.x + "%064x" % point.y
#=> "04e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f26d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c"
# 2) Compressed format (with prefix - 02 = even / 03 = odd)
# Instead of using both x and y coordinates,
# just use the x-coordinate and whether y is even/odd
prefix = point.y % 2 == 0 ? '02' : '03'
pubkey = prefix + "%064x" % point.x
#=> "02e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2"
or use the builtin helpers:
# 1) Uncompressed format (with prefix 04)
# Convert to 64 hexstring characters (32 bytes) in length
point.to_s # or point.to_s( :uncompressed )
#=> "04e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f26d2ee9a82d4158f164ae653e9c6fa7f982ed8c94347fc05c2d068ff1d38b304c"
# 2) Compressed format (with prefix - 02 = even / 03 = odd)
# Instead of using both x and y coordinates,
# just use the x-coordinate and whether y is even/odd
point.to_s( :compressed )
#=> "02e37648435c60dcd181b3d41d50857ba5b5abebe279429aa76558f6653f1658f2"
Export
To export a private or public key to
the Privacy Enhanced Mail (PEM) format use to_pem
:
private_key = EC::PrivateKey.generate
private_key.to_pem
#=> "-----BEGIN EC PRIVATE KEY-----
# MHQCAQEEIDIWkCIC58Yo1E5noSiXbHdR/8zUqB+vvTK4nSk8tZ1RoAcGBSuBBAAK
# oUQDQgAEoll8rYerfDH4q6nT1miTZZ315a8BFsKA13Z8Zif5Mh+qavIr/6HpI/Kq
# Q0bnuOZiCD9gpEIWo7VGN8wJgcu6ZA==
# -----END EC PRIVATE KEY-----"
public_key = private_key.public_key
public_key.to_pem
#=> "-----BEGIN PUBLIC KEY-----
# MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEoll8rYerfDH4q6nT1miTZZ315a8BFsKA
# 13Z8Zif5Mh+qavIr/6HpI/KqQ0bnuOZiCD9gpEIWo7VGN8wJgcu6ZA==
# -----END PUBLIC KEY-----"
To export a private or public key to
the (binary) Distinguished Encoding Rules (DER)
in Abstract Syntax Notation One (ASN.1) format use to_der
:
private_key.to_der
#=> "\xA1D\x03B\x00\x04\xA2Y|\xAD\x87\xAB|1
# \xF8\xAB\xA9\xD3\xD6h\x93e\x9D\xF5\xE5\xAF\x01\x16\xC2\x80
# \xD7v|f'\xF92\x1F\xAAj\xF2+\xFF\xA1\xE9#\xF2\xAACF\xE7\xB8
# \xE6b\b?`\xA4B\x16\xA3\xB5F7\xCC\t\x81\xCB\xBAd"
public_key = private_key.public_key
public_key.to_der
#=> "0V0\x10\x06\a*\x86H\xCE=\x02\x01\x06\x05+\x81\x04\x00
# \x03B\x00\x04\xA2Y|\xAD\x87\xAB|1\xF8\xAB\xA9
# \xD3\xD6h\x93e\x9D\xF5\xE5\xAF\x01\x16\xC2\x80\xD7v|f'\xF92
# \x1F\xAAj\xF2+\xFF\xA1\xE9#\xF2\xAACF\xE7\xB8\xE6b
# \b?`\xA4B\x16\xA3\xB5F7\xCC\t\x81\xCB\xBAd"
To export a private or public key to
the Base64-encoded Distinguished Encoding Rules (DER)
in Abstract Syntax Notation One (ASN.1) format use to_base64
:
private_key.to_base64
#=> "MHQCAQEEIDIWkCIC58Yo1E5noSiXbHdR/8zUqB+vvTK4nSk8tZ1RoAcGBSuBBAAK
# oUQDQgAEoll8rYerfDH4q6nT1miTZZ315a8BFsKA13Z8Zif5Mh+qavIr/6HpI/Kq
# Q0bnuOZiCD9gpEIWo7VGN8wJgcu6ZA=="
public_key = private_key.public_key
public_key.to_base64
#=> "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEoll8rYerfDH4q6nT1miTZZ315a8BFsKA
# 13Z8Zif5Mh+qavIr/6HpI/KqQ0bnuOZiCD9gpEIWo7VGN8wJgcu6ZA=="
Import
To import a private or public key in the PEM or DER format use the all-in-one convenience constructor:
private_key = EC::PrivateKey.new( "-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIDIWkCIC58Yo1E5noSiXbHdR/8zUqB+vvTK4nSk8tZ1RoAcGBSuBBAAK
oUQDQgAEoll8rYerfDH4q6nT1miTZZ315a8BFsKA13Z8Zif5Mh+qavIr/6HpI/Kq
Q0bnuOZiCD9gpEIWo7VGN8wJgcu6ZA==
-----END EC PRIVATE KEY-----" )
public_key = EC::PublicKey.new( "-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEoll8rYerfDH4q6nT1miTZZ315a8BFsKA
13Z8Zif5Mh+qavIr/6HpI/KqQ0bnuOZiCD9gpEIWo7VGN8wJgcu6ZA==
-----END PUBLIC KEY-----" )
## or
private_key = EC::PrivateKey.new( "\xA1D\x03B\x00\x04\xA2Y|\xAD\x87\xAB|1
\xF8\xAB\xA9\xD3\xD6h\x93e\x9D\xF5\xE5\xAF\x01\x16\xC2\x80
\xD7v|f'\xF92\x1F\xAAj\xF2+\xFF\xA1\xE9#\xF2\xAACF\xE7\xB8
\xE6b\b?`\xA4B\x16\xA3\xB5F7\xCC\t\x81\xCB\xBAd".b )
public_key = EC::PublicKey.new( "0V0\x10\x06\a*\x86H\xCE=\x02\x01\x06\x05+\x81\x04\x00
\x03B\x00\x04\xA2Y|\xAD\x87\xAB|1\xF8\xAB\xA9
\xD3\xD6h\x93e\x9D\xF5\xE5\xAF\x01\x16\xC2\x80\xD7v|f'\xF92
\x1F\xAAj\xF2+\xFF\xA1\xE9#\xF2\xAACF\xE7\xB8\xE6b
\b?`\xA4B\x16\xA3\xB5F7\xCC\t\x81\xCB\xBAd".b )
or use the decode/from helper:
private_key = EC::PrivateKey.decode_pem( ... ) # or from_pem( ... )
EC::PrivateKey.decode_der( ... ) # or from_der( ... )
EC::PrivateKey.decode_base64( ... ) # or from_base64( ... )
public_key = EC::PublicKey.decode_pem( ... ) # or from_pem( ... )
EC::PublicKey.decode_der( ... ) # or from_der( ... )
EC::PublicKey.decode_base64( ... ) # or from_base64( ... )
That's it.
Elliptic-curve cryptography (ECC) is an approach to public-key cryptography based on the algebraic structure of elliptic curves over finite fields.
(Source: Elliptic-curve cryptography @ Wikipedia)
What's an Elliptic Curve?
This is a graph of secp256k1's elliptic curve
y² = x³ + 7
over the real numbers. Note that because secp256k1 is actually defined over the field Zₚ, its graph will in reality look like random scattered points, not anything like this.(Source: Secp256k1 @ Bitcoin Wiki)
Bitcon Public Service Announcement:
If we all buy Bitcoin from one another at ever higher prices we'll all be rich beyond our wildest dreams.
-- Trolly McTrollface
BEWARE: Yes, Bitcoin Is a Ponzi - Learn How the Investment Fraud Works »
Just install the gem:
$ gem install elliptic
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!