If you're not familiar with Bitcoin, Bitcoin is essentially a P2P currency that has increased an order of magnitude in value within the last year. This video does a good job of explaining it. There are a number of libraries to work with Bitcoin in some of the most popular languages: C, Java, C#, Ruby, Python, Go, and JavaScript. This article will focus exclusively on the JavaScript library.
Disclaimer: I am not a cryptographer and any such cryptography advice or implementations should be accepted as academic experimentation and not crypto best practices.
I'd be remiss if I didn't mention anything about random number generation. Random number generation is the basis of most cryptography and Bitcoin. Your Bitcoin addresses are only as secure as your random number generator. A random number generator that is said to be cryptographically secure if it is good enough to use for cryptography in that there is enough entropy for a person to predict the number generator. Math.random()
is not cryptographically secure. This is because Math.random()
is predictable. If it's predictable, an attacker could figure out your private key from your public key. The implications of someone else knowing your private key means that they can also spend your Bitcoins.
At the time of this writing, the predominant JavaScript Bitcoin library uses CryptoJS which surprisingly uses Math.random()
. This article shows how you can use the up and coming window.crytpo
standard or the Stanford JavaScript Crypto Library
further reading:
You're going to want to use the latest BitcoinJS client lib. It's pretty outdated though. There are some more recent forks: 1, 2, and the one that I will eventually maintain. For now, use the outdated one, from BitcoinJS.
I have included the library on this page. Just open up your Chrome Console or Firefox Console and start typing along.
Bitcoin derives its security from the public-key crypto scheme Elliptic Curve Cryptography (ECC). So why did the designer of Bitcoin, Satoshi Nakamoto, decide to use ECC over the prevalent RSA crypto scheme? The primary benefit is the key size. According to the Wikipedia article on ECC, "a 256-bit ECC public key should provide comparable security to a 3072-bit RSA public key".
The Elliptical Curve Cryptography spec 2.2.1 states that the cryptography is governed by the equation:
The entire Elliptic curve domain is a sextuple, spec 3.1.1:
The precise details are out of scope for this article, read the spec for more info. Bitcoin uses the secp256k1 (info on 2.7.1) implementation, which uses Koblitz curves.
The sextuple parameters for secpk256k1 are:
thus reducing the elliptic curve equation to:
you really don't need to understand much of this. I mainly presented this material for academic purposes.
Private keys are what allows you to spend your coins. A private key, d
is any random number between 1
and n - 1
. According to the spec (3.2.1): "an elliptic
curve key pair (d, Q)
associated with T
consists of an elliptic curve secret key d
which is an integer in the interval [1, n - 1]
, and an elliptic curve public key
You'll notice that we generate 32 random values. And n
does not have a maximum of
let's generate a private key:
var randArr = new Uint8Array(32) //create a typed array of 32 bytes (256 bits)
window.crypto.getRandomValues(randArr) //populate array with cryptographically secure random numbers
//some Bitcoin and Crypto methods don't like Uint8Array for input. They expect regular JS arrays.
var privateKeyBytes = []
for (var i = 0; i < randArr.length; ++i)
privateKeyBytes[i] = randArr[i]
//if you want to follow the step-by-step results in this article, comment the
//previous code and uncomment the following
//var privateKeyBytes = Crypto.util.hexToBytes("1184CD2CDD640CA42CFC3A091C51D549B2F016D454B2774019C2B2D2E08529FD")
//hex string of our private key
var privateKeyHex = Crypto.util.bytesToHex(privateKeyBytes).toUpperCase()
console.log(privateKeyHex) //1184CD2CDD640CA42CFC3A091C51D549B2F016D454B2774019C2B2D2E08529FD
simple enough, huh? But wait, this doesn't look like the private keys that you see in your Bitcoin clients. So, what's going on? Private keys typically use a format called the Wallet Import Format (WIF).
The Wallet Import Format (WIF) is shorter way to encode the private key. It is a base 58 encoding of 0x80
+ private key + checksum.
generate a WIF in JS:
//add 0x80 to the front, https://en.bitcoin.it/wiki/List_of_address_prefixes
var privateKeyAndVersion = "80" + privateKeyHex
var firstSHA = Crypto.SHA256(Crypto.util.hexToBytes(privateKeyAndVersion))
var secondSHA = Crypto.SHA256(Crypto.util.hexToBytes(firstSHA))
var checksum = secondSHA.substr(0, 8).toUpperCase()
console.log(checksum) //"206EC97E"
//append checksum to end of the private key and version
var keyWithChecksum = privateKeyAndVersion + checksum
console.log(keyWithChecksum) //"801184CD2CDD640CA42CFC3A091C51D549B2F016D454B2774019C2B2D2E08529FD206EC97E"
var privateKeyWIF = Bitcoin.Base58.encode(Crypto.util.hexToBytes(keyWithChecksum))
console.log(privateKeyWIF) //"5Hx15HFGyep2CfPxsJKe2fXJsCVn5DEiyoeGGF6JZjGbTRnqfiD"
You can verify that this checks out by looking at either at http://brainwallet.org and entering the value of privateKeyHex
for the input to Secret Exponent. You can also verify http://gobittest.appspot.com/PrivateKey.
However, there is a much easier method to use:
//recall, "privateKeyBytes" is an array of random numbers
var privateKeyWIF = new Bitcoin.Address(privateKeyBytes)
privateKeyWIF.version = 0x80 //0x80 = 128, https://en.bitcoin.it/wiki/List_of_address_prefixes
privateKeyWIF = privateKeyWIF.toString()
console.log(privateKeyWIF) //"5Hx15HFGyep2CfPxsJKe2fXJsCVn5DEiyoeGGF6JZjGbTRnqfiD"
Now that we have our private key, we can generate a public key. Recall that Bitcoin keys use the secp256k1 (info on 2.7.1) parameters. Public keys are generated by:
where 0x04
and X and Y coordinates of 32 bytes each.
var curve = getSECCurveByName("secp256k1") //found in bitcoinjs-lib/src/jsbn/sec.js
//convert our random array or private key to a Big Integer
var privateKeyBN = BigInteger.fromByteArrayUnsigned(input)
var curvePt = curve.getG().multiply(privateKeyBN)
var x = curvePt.getX().toBigInteger()
var y = curvePt.getY().toBigInteger()
var publicKeyBytes = integerToBytes(x,32) //integerToBytes is found in bitcoinjs-lib/src/ecdsa.js
publicKeyBytes = publicKeyBytes.concat(integerToBytes(y,32))
publicKeyBytes.unshift(0x04)
var publicKeyHex = Crypto.util.bytesToHex(publicKeyBytes)
console.log(publicKeyHex)
/* output:
04d0988bfa799f7d7ef9ab3de97ef481cd0f75d2367ad456607647edde665d6f6
fbdd594388756a7beaf73b4822bc22d36e9bda7db82df2b8b623673eefc0b7495
*/
this output can be verified with http://brainwallet.org.
You may have heard of Compressed keys. An excellent answer on the Bitcoin Stack Exchange explains the differences:
A compressed key is just a way of storing a public key in fewer bytes (33 instead of 65). There are no compatibility or security issues because they are precisely the same keys, just stored in a different way. The original Bitcoin software didn't use compressed keys only because their use was poorly documented in OpenSSL. They have no disadvantages other than that a little bit of additional computation is needed before they can be used to validate a signature.
If you think of a public key as a point somewhere along a giant U, an uncompressed key is the X and Y coordinates of the point. A compressed key is how high up on the U the point is along with a single bit indicating whether it's on the left or right side. As you can visualize, they both encode precisely the same thing, but the compressed form requires half as much space plus one bit. (Of course, they're really points on elliptic curve secp256k1, but the concept is the same.)
-- David Schwartz
There is little reason to use uncompressed keys. Let's generate a compressed public key:
var publicKeyBytesCompressed = integerToBytes(x,32) //x from above
if (y.isEven())
publicKeyBytesCompressed.unshift(0x02)
else
publicKeyBytesCompressed.unshift(0x03)
var publicKeyHexCompressed = Crypto.util.bytesToHex(publicKeyBytesCompressed)
console.log(publicKeyHexCompressed)
/* output:
03d0988bfa799f7d7ef9ab3de97ef481cd0f75d2367ad456607647edde665d6f6f
*/
You can see that this matches up to http://brainwallet.org when Compressed is clicked.
It's possible to do all of this in many shorter steps:
//privateKeyBytes is the private key array from the top
var eckey = new Bitcoin.ECKey(privateKeyBytes)
var publicKeyHex = Crypto.util.bytesToHex(eckey.getPub())
console.log(publicKeyHex)
/* output:
04d0988bfa799f7d7ef9ab3de97ef481cd0f75d2367ad456607647edde665d6f6
fbdd594388756a7beaf73b4822bc22d36e9bda7db82df2b8b623673eefc0b7495
*/
eckey.compressed = true
var publicKeyHexCompressed = Crypto.util.bytesToHex(eckey.getPub())
You may have noticed that when you toggle back and forth between Compressed and Uncompressed on http://brainwallet.org that the Private Key changes as well. So, if you import a private key into your wallet, which public key will it use? Another good answer on Bitcoin Stack Exchange on how to deal with this:
... Thus, in order to support both, we must remember for each public/private keypair whether the normal or compressed encoding is to be used. As you point out, we also need this information when importing a private key. To do so, the "Wallet Import Format" for private keys (the base58 form, typically starting with a '5'), was extended. If the public key/address for a particular private key are to be derived from the compressed encoding of the public key, the private key gets an extra 0x01 byte at the end, resulting in a base58 form that starts with 'K' or 'L'.
So to answer your question: when importing a private key into the reference client, it will use the normal encoding for public keys if the '5' format was used for the private key, and the compressed encoding if the 'K'/'L' format was used. It doesn't make sense to try to convert one to the other: the client must use the same encoding as was used when generating the address, or the address won't match. Unfortunately, quite a lot of software doesn't support compressed public keys yet (which is a pity, as they save block chain space).
-- Pieter Wuille
let's generate our private compressed key:
var privateKeyBytesCompressed = privateKeyBytes.slice(0) //clone array
privateKeyBytesCompressed.push(0x01)
var privateKeyWIFCompressed = new Bitcoin.Address(privateKeyBytesCompressed)
privateKeyWIFCompressed.version = 0x80
privateKeyWIFCompressed = privateKeyWIFCompressed.toString()
console.log(privateKeyWIFCompressed) //KwomKti1X3tYJUUMb1TGSM2mrZk1wb1aHisUNHCQXTZq5auC2qc3
Once again, verify it matches http://brainwallet.org.
Bitcoin uses addresses as a means to receive coins from someone else. An address is a base58 encoded string of a 25 byte binary address. All Bitcoin addresses start with 1
. A person can have as many addresses as they'd like. Using more than one address is said to increase anonymity. Private keys give you access to spend money associated with an address.
The address is generated like the following:
Version = 1 byte of 0 (zero); on the test network, this is 1 byte of 111
Key hash = Version concatenated with RIPEMD-160(SHA-256(public key))
Checksum = 1st 4 bytes of SHA-256(SHA-256(Key hash))
Bitcoin Address = Base58Encode(Key hash concatenated with Checksum)
Compressed, uncompressed, zomg... yes, there are two addresses associated with each ECC private key. The procedure is exactly the same:
//could use publicKeyBytesCompressed as well
var hash160 = Crypto.RIPEMD160(Crypto.util.hexToBytes(Crypto.SHA256(publicKeyBytes)))
console.log(hash160) //"3c176e659bea0f29a3e9bf7880c112b1b31b4dc8"
var version = 0x00 //if using testnet, would use 0x6F or 111.
var hashAndBytes = Crypto.util.hexToBytes(hash160)
hashAndBytes.unshift(version)
var doubleSHA = Crypto.SHA256(Crypto.util.hexToBytes(Crypto.SHA256(hashAndBytes)))
var addressChecksum = doubleSHA.substr(0,8)
console.log(addressChecksum) //26268187
var unencodedAddress = "00" + hash160 + addressChecksum
console.log(unencodedAddress)
/* output
003c176e659bea0f29a3e9bf7880c112b1b31b4dc826268187
*/
var address = Bitcoin.Base58.encode(Crypto.util.hexToBytes(unencodedAddress))
console.log(address) //16UjcYNBG9GTK4uq2f7yYEbuifqCzoLMGS
even easier...
var address = new Bitcoin.Address(Crypto.util.hexToBytes(hash160))
address.version = 0x00 //testnet would be 0x6F
address = address.toString()
ok, the easiest of all...
var address = eckey.getBicoinAddress().toString() //"eckey" from above
console.log(address) ////16UjcYNBG9GTK4uq2f7yYEbuifqCzoLMGS
//you must generate a new one if you already called getBitcoinAddress() for the
//address representing the uncompressed version
var eckey2 = new Bitcoin.ECKey(privateKeyBytes)
eckey2.compressed = true
var addressForCompressed = eckey2.getBitcoinAddress().toString()
console.log(addressForCompressed) //1FkKMsKNJqWSDvTvETqcCeHcUQQ64kSC6s
OK, you've probably noticed it a lot. Once again, I'm going to defer to the internets and the peeps smarter than me:
From: Why does Bitcoin use two hash functions (SHA-256 and RIPEMD-160) to create an address?
RIPEMD was used because it produces the shortest hashes whose uniqueness is still sufficiently assured. This allows Bitcoin addresses to be shorter.
SHA256 is used as well because Bitcoin's use of a hash of a public key might create unique weaknesses due to unexpected interactions between RIPEMD and ECDSA (the public key signature algorithm). Interposing an additional and very different hash operation between RIPEMD and ECDSA makes it almost inconceivable that there might be a way to find address collisions that is significantly easier than brute force trying a large number of secret keys.
Essentially, it was a belt and suspenders approach. Bitcoin had to do something unique and rather than have to hope they got it exactly right, they overdesigned it.
-- David Schwartz
and on the SHA256(SHA256(input)):
So why does he hash twice? I suspect it's in order to prevent length-extension attacks.
SHA-2, like all Merkle-Damgard hashes suffers from a property called "length-extension". This allows an attacker who knows H(x) to calculate H(x||y) without knowing x. This is usually not a problem, but there are some uses where it totally breaks the security. The most relevant example is using H(k||m) as MAC, where an attacker can easily calculate a MAC for m||m'. I don't think Bitcoin ever uses hashes in a way that would suffer from length extensions, but I guess Satoshi went with the safe choice of preventing it everywhere.
To avoid this property, Ferguson and Schneier suggested using SHA256d = SHA256(SHA256(x)) which avoids length-extension attacks. This construction has some minor weaknesses (not relevant to bitcoin), so I wouldn't recommend it for new protocols, and would use HMAC with constant key, or truncated SHA512 instead.
Answered by CodesInChaos
Compressed keys are the preferred format now. Let's do this in one quick JS snippet to make everything stupidly simple and compatible with pretty much every Bitcoin client:
var randArr = new Uint8Array(32) //create a typed array of 32 bytes (256 bits)
window.crypto.getRandomValues(randArr) //populate array with cryptographically secure random numbers
//some Bitcoin and Crypto methods don't like Uint8Array for input. They expect regular JS arrays.
var privateKeyBytes = []
for (var i = 0; i < randArr.length; ++i)
privateKeyBytes[i] = randArr[i]
var eckey = new Bitcoin.ECKey(privateKeyBytes)
eckey.compressed = true
var address = eckey.getBitcoinAddress().toString()
console.log(address)// 1FkKMsKNJqWSDvTvETqcCeHcUQQ64kSC6s
var privateKeyBytesCompressed = privateKeyBytes.slice(0) //clone array
privateKeyBytesCompressed.push(0x01)
var privateKeyWIFCompressed = new Bitcoin.Address(privateKeyBytesCompressed)
privateKeyWIFCompressed.version = 0x80
privateKeyWIFCompressed = privateKeyWIFCompressed.toString()
console.log(privateKeyWIFCompressed) //KwomKti1X3tYJUUMb1TGSM2mrZk1wb1aHisUNHCQXTZq5auC2qc3
There you have it! KwomKti1X3tYJUUMb1TGSM2mrZk1wb1aHisUNHCQXTZq5auC2qc3 is your private key in WIF form that allows you to send money for address 1FkKMsKNJqWSDvTvETqcCeHcUQQ64kSC6s.
You can generate private keys from a passphrase. One method would be to just calculate the double SHA256 hash on the user passphrase and use that as the private key.
var password = "there can be only one"
var privateKeyHex = Crytpo.util.SHA256(Crypto.util.hexToBytes(Crypto.util.SHA256(password)))
var privateKeyBytes = Crypto.util.hexToBytes(privateKeyHex)
further reading:
Credits: Combing through the http://brainwallet.org was a big help.
I started a new project called CryptoCoinJS to address the shortcomings in the BitcoinJS library. Check it out here: https://github.com/cryptocoinjs
If you made it this far, you should follow me on Twitter.
-JP
Want to test-drive Bitcoin without any risk? Check out my bitcoin wallet Coinbolt. It includes test coins for free.
comments powered by Disqus