Messing with Bitcoin Keys and Addresses

The “bu” tool is obsolete, which makes this post not-so-useful. Look at this file instead.

The command line utility “bu” (for “Bitcoin utilities”) is included with my Python-based pycoin library. This utility makes it easy to deal with Bitcoin private keys and addresses in their native and various intermediate formats. Let’s go through some examples.

The most basic form of a Bitcoin private key is simply an integer between 1 and 115792089237316195423570985008687907852837564279074904382605163141518161494336 ≅ 1.15e77 (inclusive). That’s it! This integer is a “secret exponent”, because generating the public key involves exponentiation, and there is no known way to go from the public key to the secret exponent.

Let’s take a look at the very first private key, also known as “1”.


$ bu 1
secret exponent: 1
  hex:           1
WIF:             KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn
  uncompressed:  5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf
public pair x:   55066263022277343669578718895168534326250603453777594175500187360389116729240
public pair y:   32670510020758816978083085130507043184471273380659243275938904335757337482424
  x as hex:      79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
  y as hex:      483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
y parity:        even
key pair as sec: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
  uncompressed:  0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\
                   483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
hash160:         751e76e8199196d454941c45d1b3a323f1433bd6
  uncompressed:  91b24bf9f5288532960ac687abb035127b1d28a5
Bitcoin address: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
  uncompressed:  1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm

You can see from blockchain.info that the addresses corresponding to this private key (1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH and 1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm) are used a lot in tests. Of course, neither has any funds in it (well, at least not at this time), since draining the funds is as simple as entering one of the WIF values above into a Bitcoin client.

There is a bunch of information here. The secret exponent is displayed in decimal and in hex.

The corresponding WIF ("wallet import format") key is displayed, both in compressed and uncompressed format; with this information, you can import the corresponding bitcoin address into your client. Note that the WIF simply contains the exponent encoded using "hashed base 58".

The "hashed base 58" encoding is used to represent an integer with a checksum for validity. A 32-bit checksum is appended to the binary form of the integer, forming another integer. This integer is then represented in base 58 using the alphabet of all digits and all letters of the upper and lower case English alphabet except 0, o, O and l (presumably left out because of potential confusion).

So encoding the WIF in this format really provides no additional (non-redundant) information beyond the secret exponent.

The public pair x and y correspond to the ECDSA (elliptical curve digital signature algorithm) public key that is used to verify digital signatures. Bitcoin clients use public keys to validate that transactions are signed by an entity that has knowledge of the corresponding secret exponent. The x, y value is on the elliptical curve used by bitcoin. In other words

y*y = x*x*x + 7 (mod P)

where P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f

You can check this easily in Python:


$ python
Python 3.3.0 (default, Mar 21 2013, 20:48:16) 
[GCC 4.2.1 Compatible Apple Clang 4.0 ((tags/Apple/clang-421.0.60))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
>>> y = 32670510020758816978083085130507043184471273380659243275938904335757337482424
>>> x = 55066263022277343669578718895168534326250603453777594175500187360389116729240
>>> (x*x*x+7) % p
32748224938747404814623910738487752935528512903530129802856995983256684603122
>>> (y * y) % p
32748224938747404814623910738487752935528512903530129802856995983256684603122
>>> print "ta da!"
ta da!

For a given x value, you can rewrite as y = sqrt(x^3+7) (mod P). Since numbers have two square roots even in a finite field, there are two values y0 and y1 that satisfy this equation, where y1 = P - y0. Since P is odd, exactly one of y0 and y1 is even, and the other is odd. In other words, with x and knowledge of whether y is even or odd, we can figure out the value for y. (This is how compressed keys work... they include the value for x along with a boolean indicating even or odd rather than the full value for y.)

The SEC ("Standards for Efficient Cryptography") format provides an alternate way of encoding the public key. This is the internal format that Bitcoin uses in transaction signatures to encode public keys. There is an uncompressed format, which has a prefix of a single 04 byte, followed by the x and y coordinates, and a compressed format, which has a prefix of 02 or 03 depending upon whether the y coordinate is even or odd, followed by the x coordinate.

The hash160 value is the ripemd160 hash of the sha256 hash of the bytestream of the sec version of the key.


$ python
Python 2.7.2 (default, Oct 11 2012, 20:14:37) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import binascii, hashlib
>>> sec = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
>>> binascii.hexlify(hashlib.new("ripemd", hashlib.sha256(binascii.unhexlify(sec)).digest()).digest()) 
'751e76e8199196d454941c45d1b3a323f1433bd6'

The Bitcoin address is the hashed base 58 representation of the hash160 value.

The bu utility will accept input in nearly any format, automatically determining the input type, and display output of all values that can calculated. (Obviously if you enter a Bitcoin address, you won't get the corresponding WIF!)


$ bu 55066263022277343669578718895168534326250603453777594175500187360389116729240,32670510020758816978083085130507043184471273380659243275938904335757337482424
public pair x:   55066263022277343669578718895168534326250603453777594175500187360389116729240
public pair y:   32670510020758816978083085130507043184471273380659243275938904335757337482424
  x as hex:      79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
  y as hex:      483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
even
key pair as sec: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
  uncompressed:  0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\
                   483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
hash160:         751e76e8199196d454941c45d1b3a323f1433bd6
  uncompressed:  91b24bf9f5288532960ac687abb035127b1d28a5
Bitcoin address: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
  uncompressed:  1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm

The input is determined to be x & y coordinates.


$ bu 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
hash160:         751e76e8199196d454941c45d1b3a323f1433bd6
Bitcoin address: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH

The input here is determined to be a Bitcoin address. The only thing that it can be converted to is a hash160.

Let's try one more example using the WIF from https://en.bitcoin.it/wiki/WIF :


$ bu 0C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D
secret exponent: 5500171714335001507730457227127633683517613019341760098818554179534751705629
  hex:           c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d
WIF:             KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617
  uncompressed:  5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ
public pair x:   94473386280621915394287615869907363252910868562986308188178980306950346138716
public pair y:   97844737324952875321726721826250953720280326724356638601167669885622888745738
  x as hex:      d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c
  y as hex:      d85228a6fb29940e858e7e55842ae2bd115d1ed7cc0e82d934e929c97648cb0a
y parity:        even
key pair as sec: 02d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c
  uncompressed:  04d0de0aaeaefad02b8bdc8a01a1b8b11c696bd3d66a2c5f10780d95b7df42645c\
                   d85228a6fb29940e858e7e55842ae2bd115d1ed7cc0e82d934e929c97648cb0a
hash160:         d9351dcbad5b8f3b8bfa2f2cdc85c28118ca9326
  uncompressed:  a65d1a239d4ec666643d350c7bb8fc44d2881128
Bitcoin address: 1LoVGDgRs9hTfTNJNuXKSpywcbdvwRXpmK
  uncompressed:  1GAehh7TsJAHuUAeKZcXf5CnwuGuGgyX2S

Pretty snazzy, eh?

15 thoughts on “Messing with Bitcoin Keys and Addresses

  1. I’m really liking your pycoin implementation. The code seems really cleanly written and the BIP0032 implementation is very useful.

    I wondering will you be implementing support for creating BIP0011/M-of-N Bitcoin transactions and for creating and signing transactions to spend from those addresses?

  2. The VM in pycoin does not currently support OP_CHECKMULTISIG, which would be the first requirement in validating M-of-N transactions.

    Thinking out loud here… it seems creating an M-of-N transaction would be simple… just fill in a template script in the TxOut.

    Signing M of the keys might be fairly straightforward to do all at once, but that’s not a very useful use case, since generally one person does not have access to M of the private keys. I think what we’d really need is support for “partial transactions”, so one of the M could create a partially signed transaction, pass it on to the next person, who could then add a signature, and so on. So doing this would be a bit of work, since a partial transaction would have to be created.

    Are there any M-of-N transactions in the blockchain? (It seems there must be.) Finding one would be a good first step.

  3. How do you propose people make wallets, do we really want pure random or could someone make a WIF based on the sha256 of a very good passphrase?

    In this last case nothing needs to be stored in order to be able to access the bitcoins.

    Next to that, is this a handy way to generate WIF’s offline and only bring the BTC address to the internetconnected world?

    import hashlib
    from pycoin import ecdsa, encoding
    import os
    rand = os.urandom(32).encode(‘hex’)
    secret_exponent= int(“0x”+rand, 0)
    print encoding.secret_exponent_to_wif(secret_exponent, compressed=True)
    public_pair = ecdsa.public_pair_for_secret_exponent(ecdsa.secp256k1.generator_secp256k1, secret_exponent)
    hash160 = encoding.public_pair_to_hash160_sec(public_pair, compressed=True)
    print(“Bitcoin address: %s” % encoding.hash160_sec_to_bitcoin_address(hash160))

  4. either there is still a bug in there or some private keys won’t have BTC adresses :}


    >>> import hashlib
    >>> from pycoin import ecdsa, encoding
    >>> import os
    >>> rand = os.urandom(32).encode('hex')
    >>> secret_exponent= int("0x"+rand, 0)
    >>> print encoding.secret_exponent_to_wif(secret_exponent, compressed=True)
    L1U9exPrNLKxHHXemCYeG1aC98qtXvPedqg3XAHFC9tnUxw1aJYv
    >>> public_pair = ecdsa.public_pair_for_secret_exponent(ecdsa.secp256k1.generator_secp256k1, secret_exponent)
    >>> hash160 = encoding.public_pair_to_hash160_sec(public_pair, compressed=True)
    >>> print("Bitcoin address: %s" % encoding.hash160_sec_to_bitcoin_address(hash160))
    Bitcoin address: 19hr8ctQtiwm4U3gECPETuzn2ez2Q4L3oz
    >>>
    >>> rand = '1'
    >>> secret_exponent= int("0x"+rand, 0)
    >>> print encoding.secret_exponent_to_wif(secret_exponent, compressed=True)
    KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn
    >>> public_pair = ecdsa.public_pair_for_secret_exponent(ecdsa.secp256k1.generator_secp256k1, secret_exponent)
    >>> hash160 = encoding.public_pair_to_hash160_sec(public_pair, compressed=True)
    >>> print("Bitcoin address: %s" % encoding.hash160_sec_to_bitcoin_address(hash160))
    Bitcoin address: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
    >>> rand = '0'
    >>> secret_exponent= int("0x"+rand, 0)
    >>> print encoding.secret_exponent_to_wif(secret_exponent, compressed=True)
    KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73Nd2Mcv1
    >>> public_pair = ecdsa.public_pair_for_secret_exponent(ecdsa.secp256k1.generator_secp256k1, secret_exponent)
    >>> hash160 = encoding.public_pair_to_hash160_sec(public_pair, compressed=True)
    Traceback (most recent call last):
    File "", line 1, in
    File "build/bdist.macosx-10.9-intel/egg/pycoin/encoding.py", line 226, in public_pair_to_hash160_sec
    File "build/bdist.macosx-10.9-intel/egg/pycoin/encoding.py", line 197, in public_pair_to_sec
    TypeError: unsupported operand type(s) for &: 'NoneType' and 'int'
    >>> print("Bitcoin address: %s" % encoding.hash160_sec_to_bitcoin_address(hash160))

  5. In your failure case, your secret exponent is 0, which is invalid. The secret exponent has to be an integer between 1 and 115792089237316195423570985008687907852837564279074904382605163141518161494336.

  6. > How do you propose people make wallets, do we really want pure random or could someone make a WIF based on the sha256 of a very good passphrase?

    I’m no expert on this. But I think it’s a better idea to use one or more sources of entropy to generate your initial wallet key, because people just are not good at coming up with passphrases. If it’s any text that exists anywhere on the internet (or offline! since it may be transfered to the internet in future), it’s probably a bad password.

    Instead, I’d recommend hiding the initial wallet key in a local GPG-encrypted file (or offline) so that it’s something you have (the GPG-encrypted file) plus something you know (the GPG password).

    > Next to that, is this a handy way to generate WIF’s offline and only bring the BTC address to the internetconnected world?

    BIP0032 makes this easier, yes.

  7. Is there any documentation for this utility?

    What do the various breakdown of the keys represent, step by step?

  8. “The Bitcoin address is the hashed base 58 representation of the hash160 value.”
    __________

    A Bitcoin Address is a 200-bit [ 8+160+32 ] Object
    [Type8+Hash160+Checksum32]

    Checksum32 is computed over the 168-bit fields [Type8+Hash160]
    First 4 bytes from SHA-256(SHA-256(168-bit field))

    The 200-bit [ 8+160+32 ] Object is then converted using Base58
    The value of Type8 influences the Left (Vanity) Symbol in the Address
    Brute-Force searches for 160 values can yield larger Vanity Strings

    Base58 Symbols are: o is valid – l is not along with 0,I,O “123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz”

    NOTE: Bitcoins sent to a Valid Address with no ECDSA Keys known are likely lost [stuck]. Finding BOTH a Private and Public Key is unlikely. The platform
    allows coins to be sent to BlackHoles:
    1BitcoinEaterAddressDontSendf59kuE

  9. Good job on your contribution, it’s a really cool tool. My question is how do we turn your genwallet tool and a BIP32 web client without using the wallet.dat file. Why can’t I use genwallet to generate my HD addresses and then switch to whatever wallet m/1/0 I wanted and do work, then switch to a different wallet m/1/0/1.
    I can maybe make virtual environments when the webclient logs in. Sorry just throwing some questions out. I want to build a safe BIP32 web wallet for the masses, without storing the private keys. No private Key – No coins – The world needs a safe wallet help.

  10. Hi Richard,
    Having issues installing (Python newb here)…
    Downloaded Pycoin from github as a zip, uninstalled contents into folder C:\Python34\Lib\pycoin then I use “python setup.py install” but getting following error:

    C:\Python34\Lib\pycoin>python setup.py install
    Traceback (most recent call last):
    File “setup.py”, line 3, in
    from setuptools import setup
    ImportError: No module named ‘setuptools’

    Your tool looks fantastic and would love to try it out so any help greatly appreciated. I’ll see if I can’t figure it out with a bit of Googling etc but any input would be much appreciated!

Comments are closed.