├── .gitignore ├── README.md ├── base58passphrase ├── create_new_cold_storage ├── format_key.py ├── format_key_tests.py ├── gen_cold_wallet ├── ocraextended.ttf ├── sample_keys.html ├── sample_keys.pdf ├── sample_keys.tex └── sample_keys.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.aux 3 | *-pics.pdf 4 | .DS_Store 5 | *~ 6 | sample_keys.synctex.gz 7 | *.pyc 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cold Wallet Generator 2 | ===================== 3 | 4 | Given one or more Bitcoin addresses with private keys, generates a TeX or HTML file suitable for printing and putting into cold storage. Now with BIP 0038 symmetric encryption. 5 | 6 | Requirements 7 | ============ 8 | 9 | Python and [jinja2](http://jinja.pocoo.org/docs/). For TeX: something that can render TeX files. For HTML: the Python libraries [PIL](http://www.pythonware.com/products/pil/) and [qrcode](https://github.com/lincolnloop/python-qrcode), as well as something that can render HTML (either your web browser or a direct-to-PDF tool like [wkhtmltopdf](https://code.google.com/p/wkhtmltopdf/)). 10 | 11 | For format_key: sudo pip install base58 pycrypto ecdsa scrypt 12 | 13 | Before you begin 14 | === 15 | 16 | Have a look at https://github.com/bpdavenport/btc as an alternative. It's substantially easier to use than this set of scripts, and it requires only Python libraries. 17 | 18 | Usage 19 | ===== 20 | 21 | 1. Get a file of Bitcoin addresses with private keys that you want to put into cold storage (i.e., offline, not on a computer). The addresses should be one per line, in Electrum format: `address:private_key`. 22 | 2. `cat my_keys.txt | gen_cold_wallet > my_keys.tex` 23 | 3. Options include `--exclude-private-keys`, `--exclude-private-key-text`, and `--html`. Default is to print private keys, both as QR codes and as text, and to output as TeX. 24 | 4. Either run the TeX file through your favorite TeX processor and print the result, or print the HTML file. 25 | 5. Test the printed page with a QR-code scanner and make sure you can read the private keys. You might want to print another copy without the private keys so that you can easily send Bitcoin to the addresses later on. 26 | 6. Securely delete the input key file, the TeX/HTML file, and any intermediate files. 27 | 7. Put the printed paper somewhere safe. 28 | 8. Use your addresses as you wish. 29 | 30 | Notes 31 | ====== 32 | 33 | While developing this script, I used format_key, based on [keyfmt](https://github.com/bkkcoins/misc/blob/master/keyfmt/keyfmt), and piped its output straight to the wallet generator: 34 | 35 | `$ { for i in {1..20} ; do hexdump -v -e '/1 "%02X"' -n 32 /dev/urandom | format_key.py "%a:%w" ; done } | 36 | gen_cold_wallet > /tmp/keys.tex` 37 | 38 | (For real cold-storage addresses, if you're paranoid you'll use something other than /dev/urandom for your source of entropy.) On Linux, you probably want to install texlive and then run `pdflatex --shell-escape keys.tex`. On Mac, install [MacTex](http://tug.org/mactex/). No idea what to do on Windows. 39 | 40 | If you don't want to worry about files sticking around on Linux, avoid writing them in the first place with `mkdir /tmp/ramdisk; chmod 777 /tmp/ramdisk; sudo mount -t tmpfs -o size=256M tmpfs /tmp/ramdisk/` and then do your work in that directory. Then `umount /tmp/ramdisk/` when you're done. 41 | 42 | The TeX output is designed to be printed on 8.5-inch by 11-inch paper, 10 addresses per page, with the long end of the printed pages folded in half. Then the folded pages fit nicely in a small 5.5-inch x 8.5-inch binder. I couldn't figure out how to render the same thing in HTML, so I recommend a 2-per-page layout when printing, which ends up looking the same. 43 | 44 | The create_new_cold_storage script is the one I use to create a fresh set of BIP 0038-encrypted keys and addresses. Run it as follows: 45 | 46 | ` PASSPHRASE=secret GPG_KEY_ID=1234ABCD create_new_cold_storage` 47 | 48 | (Note that there's a space at the start of the command! If your Bash shell is set up with HISTCONTROL=ignorespace, this will keep your passphrase from being written to your Bash history.) 49 | 50 | This will create the following: 51 | 52 | 1. A plaintext file containing the addresses. 53 | 2. A PDF of the addresses with scannable QR codes. 54 | 3. A GPG-encrypted file containing keys and addresses, where the GPG key is the one having the ID you specified earlier. 55 | 4. A PDF of BIP 0038-encrypted keys and addresses, using the passphrase you specified earlier. 56 | 57 | Run the script on an offline computer, connect the computer by USB to a printer, print the PDF with the private keys, save that paper somewhere safe, then securely delete that PDF. Finally, move the other three files onto normal storage. For the GPG-encrypted file, keep it on well-replicated storage (Google Drive, Dropbox, email to friends, email to enemies, pastebin, anywhere you like). 58 | 59 | FAQ 60 | === 61 | 62 | * **What's this about secure deletion?** On OSX, you want `srm file_I_never_want_to_see_again`. On Linux, it's `shred -u file_I_never_want_to_see_again`. I don't know how to do it on Windows. 63 | 64 | * **TeX? Seriously? If I wanted low tech I'd carve my addresses on rocks.** It's the best toolchain I could think of that gave good control over page layout but didn't involve a web browser (which you might not trust not to leave traces behind). There's probably a better command-line page-layout tool that will generate printable content, but I'm not familiar with it. 65 | 66 | * **This is just what I was looking for! May I send you a tiny thank-you?** Sure! Here's my address: [1BUGzQ7CiHF2FUxHVH2LbUx1oNNN9VnuC1](https://blockchain.info/address/1BUGzQ7CiHF2FUxHVH2LbUx1oNNN9VnuC1) 67 | -------------------------------------------------------------------------------- /base58passphrase: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Pass in some random bytes, and this utility will turn it into a 4 | # 256-bit base58-encoded number that's suitable as a passphrase. Example: 5 | # 6 | # < /dev/random head -c 1024 | base58passphrase 7 | 8 | import hashlib 9 | import sys 10 | 11 | alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' 12 | base_count = len(alphabet) 13 | 14 | # 256 / log2(58), rounded up 15 | PASSPHRASE_LENGTH = 44 16 | 17 | # https://gist.github.com/ianoxley/865912 18 | def encode(num): 19 | """ Returns num in a base58-encoded string """ 20 | encode = '' 21 | 22 | if (num < 0): 23 | return '' 24 | 25 | while (num >= base_count): 26 | mod = num % base_count 27 | encode = alphabet[mod] + encode 28 | num = num / base_count 29 | 30 | if (num): 31 | encode = alphabet[num] + encode 32 | 33 | return encode 34 | 35 | input = sys.stdin.read() 36 | 37 | hash = '' 38 | hash = hashlib.sha512(hash + input).digest() 39 | hash = hashlib.sha512(hash + input).digest() 40 | hash = hashlib.sha384(hash + input).digest() 41 | hash = hashlib.sha384(hash + input).digest() 42 | hash = hashlib.sha256(hash + input).digest() 43 | hash = hashlib.sha256(hash + input).digest() 44 | encoded = '' 45 | 46 | # hashes starting with enough zeroes will end up encoding to something 47 | # shorter than the maximum length. Instead of coming up with a clever 48 | # way to deal with them, just keep on hashing until something else comes 49 | # up. 50 | while len(encoded) < PASSPHRASE_LENGTH: 51 | hash = hashlib.sha256(hash + input).digest() 52 | num = int(hash.encode("hex"), 16) 53 | encoded = encode(num) 54 | 55 | print encode(num), 56 | -------------------------------------------------------------------------------- /create_new_cold_storage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Before running, make sure PASSPHRASE and GPG_KEY_ID are set. 4 | # It's expected that a key matching the filename $GPG_KEY_ID.gpg is in the 5 | # current directory. 6 | # 7 | # Example: 8 | # 9 | # PASSPHRASE=teddybear GPG_KEY_ID=ABCD0123 create_new_cold_storage 10 | 11 | PREFIX=cs-btc-`date "+%Y%m%d"`-`date "+%N"` 12 | 13 | { for i in {1..20} ; do hexdump -v -e '/1 "%02X"' -n 32 /dev/urandom \ 14 | | format_key.py -p $PASSPHRASE "%a:%w:%W" ; done } | sort -u > keys.txt 15 | 16 | < keys.txt gen_cold_wallet > keys.tex 17 | pdflatex --shell-escape keys.tex 18 | mv keys.pdf $PREFIX-print-then-shred.pdf 19 | 20 | < keys.txt gen_cold_wallet -x > keys.tex 21 | pdflatex --shell-escape keys.tex 22 | mv keys.pdf $PREFIX-public-only.pdf 23 | 24 | gpg --import ${GPG_KEY_ID}.gpg 25 | gpg -a \ 26 | -r ${GPG_KEY_ID} \ 27 | --no-default-recipient --no-encrypt-to \ 28 | -o $PREFIX.txt.asc \ 29 | -e keys.txt 30 | 31 | grep -oE "^[0-9a-zA-Z]+" keys.txt > $PREFIX-addresses.txt 32 | 33 | shred -u keys* 34 | -------------------------------------------------------------------------------- /format_key.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Read a private key from stdin and output formatted data values. 4 | # Input one key per line either hex (64 chars) or WIF key (51 base 58 chars). 5 | # 6 | # eg. "Address: %a\nPrivkey: %w" outputs a format like the vanitygen program 7 | # "a:%w" outputs a format good for importing to Electrum 8 | # 9 | # This generates a new key for importing to Electrum: 10 | # 11 | # hexdump -v -e '/1 "%02X"' -n 32 /dev/urandom | format_key.py "%a:%w" 12 | # 13 | # Supplying a passphrase with -p causes %w to emit a BIP 0038-encrypted 14 | # private key. In this case, %W will emit the unencrypted WIF key. 15 | # 16 | # Adapted from https://github.com/bkkcoins/misc. 17 | # 18 | # Install dependencies with `sudo pip install base58 pycrypto ecdsa scrypt`. 19 | 20 | import argparse 21 | import base58 22 | import binascii 23 | import hashlib 24 | import sys 25 | 26 | class KeyFormatter: 27 | def __init__(self): 28 | pass 29 | 30 | def format(self, format_specifiers, lines, passphrase=None): 31 | needs_math = any(v in format_specifiers[0] for v in ['%p', '%a']) 32 | uses_passphrase = bool(passphrase) 33 | needs_math |= uses_passphrase 34 | 35 | if needs_math: 36 | import ecdsa 37 | if uses_passphrase: 38 | from Crypto.Cipher import AES 39 | import scrypt 40 | 41 | if needs_math: 42 | # secp256k1, http://www.oid-info.com/get/1.3.132.0.10 43 | _p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL 44 | _r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L 45 | _b = 0x0000000000000000000000000000000000000000000000000000000000000007L 46 | _a = 0x0000000000000000000000000000000000000000000000000000000000000000L 47 | _Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L 48 | _Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8L 49 | curve_secp256k1 = ecdsa.ellipticcurve.CurveFp(_p, _a, _b) 50 | generator_secp256k1 = ecdsa.ellipticcurve.Point(curve_secp256k1, 51 | _Gx, _Gy, _r) 52 | oid_secp256k1 = (1,3,132,0,10) 53 | SECP256k1 = ecdsa.curves.Curve("secp256k1", curve_secp256k1, 54 | generator_secp256k1, oid_secp256k1) 55 | 56 | response = [] 57 | for line in lines: 58 | line = line.strip() 59 | if line[0] == '5' and len(line) < 64: 60 | line = binascii.hexlify(base58.b58decode(line[:51])[1:33]).upper() 61 | else: 62 | line = line[:64] 63 | 64 | chksum = binascii.hexlify(hashlib.sha256(hashlib.sha256( 65 | binascii.unhexlify('80' + line)).digest()).digest()[:4]) 66 | privkey = binascii.unhexlify('80' + line + chksum) 67 | 68 | if needs_math: 69 | pubkey = chr(4) + ecdsa.SigningKey.from_secret_exponent( 70 | long(line, 16), 71 | curve=SECP256k1).get_verifying_key().to_string() 72 | rmd = hashlib.new('ripemd160') 73 | rmd.update(hashlib.sha256(pubkey).digest()) 74 | an = chr(0) + rmd.digest() 75 | 76 | # 1. Compute the Bitcoin address (ASCII), and take the first 77 | # four bytes of SHA256(SHA256()) of it. Let's call this 78 | # "addresshash". 79 | addr = an + hashlib.sha256( 80 | hashlib.sha256(an).digest()).digest()[0:4] 81 | addr_b58 = base58.b58encode(addr) 82 | addresshash = hashlib.sha256( 83 | hashlib.sha256(addr_b58).digest()).digest()[0:4] 84 | if uses_passphrase: 85 | # Derive a key from the passphrase using scrypt. 86 | # Parameters: passphrase is the passphrase itself 87 | # encoded in UTF-8. addresshash came from the earlier step, 88 | # n=16384, r=8, p=8, length=64 (n, r, p are provisional and 89 | # subject to consensus) 90 | # Let's split the resulting 64 bytes in half, and call them 91 | # derivedhalf1 and derivedhalf2. 92 | key = scrypt.hash(passphrase, addresshash, 16384, 8, 8) 93 | derivedhalf1 = key[0:32] 94 | derivedhalf2 = key[32:64] 95 | 96 | # Do AES256Encrypt(bitcoinprivkey[0...15] xor 97 | # derivedhalf1[0...15], derivedhalf2), call the 16-byte 98 | # result encryptedhalf1 99 | aes = AES.new(derivedhalf2) 100 | encryptedhalf1 = aes.encrypt(binascii.unhexlify( 101 | '%0.32x' % (long(line[0:32], 16) ^ long( 102 | binascii.hexlify(derivedhalf1[0:16]), 16)))) 103 | 104 | # Do AES256Encrypt(bitcoinprivkey[16...31] xor 105 | # derivedhalf1[16...31], derivedhalf2), call the 16-byte 106 | # result encryptedhalf2 107 | aes = AES.new(derivedhalf2) 108 | encryptedhalf2 = aes.encrypt(binascii.unhexlify( 109 | '%0.32x' % (long(line[32:64], 16) ^ long( 110 | binascii.hexlify(derivedhalf1[16:32]), 16)))) 111 | 112 | # The encrypted private key is the Base58Check-encoded 113 | # concatenation of the following, which totals 39 bytes 114 | # without Base58 checksum: 0x01 0x42 + flagbyte + salt + 115 | # encryptedhalf1 + encryptedhalf2 116 | encrypted_privkey = ('\x01\x42\xc0' + addresshash + 117 | encryptedhalf1 + encryptedhalf2) 118 | encrypted_privkey += hashlib.sha256( 119 | hashlib.sha256( 120 | encrypted_privkey).digest()).digest()[:4] 121 | 122 | out = format_specifiers[0].replace('%h', line) 123 | if uses_passphrase: 124 | out = out.replace( 125 | '%w', 126 | base58.b58encode(encrypted_privkey)).replace( 127 | '%W', base58.b58encode(privkey)) 128 | else: 129 | out = out.replace('%w', base58.b58encode(privkey)) 130 | if needs_math: 131 | out = out.replace('%p', binascii.hexlify(pubkey).upper()) 132 | out = out.replace('%a', addr_b58) 133 | response.append(out.decode('string-escape')) 134 | return '\n'.join(response) 135 | 136 | if __name__ == "__main__": 137 | parser = argparse.ArgumentParser( 138 | description='Reads a hex Bitcoin private key from stdin and ' + 139 | 'outputs formatted data.') 140 | parser.add_argument('format_specifiers', metavar='format-specifier', 141 | type=str, 142 | nargs=1, help='%%h = hex privkey, ' + 143 | '%%w = WIF privkey, ' + 144 | '%%p = public key, %%a = address') 145 | parser.add_argument('-p', metavar='passphrase', type=str, nargs='?', 146 | help='Passphrase to protect ' + 147 | 'BIP 0038 private keys') 148 | args = parser.parse_args() 149 | 150 | key_formatter = KeyFormatter() 151 | print key_formatter.format(args.format_specifiers, sys.stdin.readlines(), 152 | args.p) 153 | -------------------------------------------------------------------------------- /format_key_tests.py: -------------------------------------------------------------------------------- 1 | import format_key 2 | 3 | kf = format_key.KeyFormatter() 4 | 5 | tests = [ 6 | # BIP 0038 Example #1 7 | ("TestingOneTwoThree", 8 | "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", 9 | "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", 10 | "CBF4B9F70470856BB4F40F80B87EDB90865997FFEE6DF315AB166D713AF433A5", 11 | "1Jq6MksXQVWzrznvZzxkV6oY57oWXD9TXB", 12 | "04D2CE831DD06E5C1F5B1121EF34C2AF4BCB01B126E309234ADBC3561B60C9360EA7F23327B49BA7F10D17FAD15F068B8807DBBC9E4ACE5D4A0B40264EEFAF31A4"), 13 | 14 | # BIP 0038 Example #2 15 | ("Satoshi", 16 | "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", 17 | "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5", 18 | "09C2686880095B1A4C249EE3AC4EEA8A014F11E6F986D0B5025AC1F39AFBD9AE", 19 | "1AvKt49sui9zfzGeo8EyL8ypvAhtR2KwbL", 20 | "0463B600A0BB6A2F2BEF7BB9648222C3593A6EF5F7C2D81433C5193BF84B9F862B940E55DA162AECA6293CDE138BCC18BA978FAE399F14F258AFA4F799EE61ADCB"), 21 | 22 | # bitaddress.org test vector #1 23 | (None, 24 | None, 25 | "5J8QhiQtAiozKwyk3GCycAscg1tNaYhNdiiLey8vaDK8Bzm4znb", 26 | "292665C3872418ADF1DA7FFA3A646F2F0602246DA6098A91D229C32150F2718B", 27 | "1Cnz9ULjzBPYhDw1J8bpczDWCEXnC9HuU1", 28 | "0478982F40FA0C0B7A55717583AFC99A4EDFD301A2729DC59B0B8EB9E18692BCB521F054FAD982AF4CC1933AFD1F1B563EA779A6AA6CCE36A30B947DD653E63E44"), 29 | 30 | # bitaddress.org leading zero in public key bug 31 | (None, 32 | None, 33 | "5Je7CkWTzgdo1RpwjYhwnVKxQXt8EPRq17WZFtWcq5umQdsDtTP", 34 | "6C9565B3EEF4EF9E01C216E1910763A5F94CF3654C059E8C67A348D10AE39C28", 35 | "1M6dsMZUjFxjdwsyVk8nJytWcfr9tfUa9E", 36 | "040076D4852E6E0311E080036EFEC96A339540176CF53EB7182580BC81326EC9B16D78DF5918E18EF1F57AE0591541D268DEE528BF5C29E963899946D300F90358"), 37 | 38 | # bitaddress.org leading zero in private key bug 39 | (None, 40 | None, 41 | "5HpJ4bpHFEMWYwCidjtZHwM2rsMh4PRfmZKV8Y21i7msiUkQKUW", 42 | "0004D30DA67214FA65A41A6493576944C7EA86713B14DB437446C7A8DF8E13DA", 43 | "1NAjZjF81YGfiJ3rTKc7jf1nmZ26KN7Gkn", 44 | "049848B6EAE15C2E078FE12EF779F4E322DEF5241C0B213D36D327A78B4EB744D379FD3C3C0D44FC99FDAE161C45BBC458CF84BA5F437229FD6D1DE9CE483C2F0B") 45 | ] 46 | 47 | for test in tests: 48 | # Unencrypted, hex 49 | lines = [test[3]] 50 | out = kf.format(["%a:%h:%p:%w"], lines, None) 51 | expected = "%s:%s:%s:%s" % (test[4], test[3], test[5], test[2]) 52 | assert expected == out, "Unencrypted/hex: expected %s but got %s" % ( 53 | expected, out) 54 | 55 | # Unencrypted, WIF 56 | lines = [test[2]] 57 | out = kf.format(["%a:%h:%p:%w"], lines, None) 58 | expected = "%s:%s:%s:%s" % (test[4], test[3], test[5], test[2]) 59 | assert expected == out, "Unencrypted/WIF: expected %s but got %s" % ( 60 | expected, out) 61 | 62 | if test[0]: 63 | # Encrypted, hex 64 | lines = [test[3]] 65 | out = kf.format(["%a:%h:%p:%w:%W"], lines, test[0]) 66 | expected = "%s:%s:%s:%s:%s" % ( 67 | test[4], test[3], test[5], test[1], test[2]) 68 | assert expected == out, "Encrypted/hex: expected %s but got %s" % ( 69 | expected, out) 70 | 71 | # Encrypted, WIF 72 | lines = [test[2]] 73 | out = kf.format(["%a:%h:%p:%w:%W"], lines, test[0]) 74 | expected = "%s:%s:%s:%s:%s" % ( 75 | test[4], test[3], test[5], test[1], test[2]) 76 | assert expected == out, "Encrypted/hex: expected %s but got %s" % ( 77 | expected, out) 78 | -------------------------------------------------------------------------------- /gen_cold_wallet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # https://github.com/sowbug/cold-wallet-generator 4 | # 5 | # Generates a TeX or HTML file representing a nice-looking, scannable paper 6 | # Bitcoin wallet suitable for cold storage. 7 | # 8 | # Accepts a series of addresses on stdin, where each line contains an 9 | # Electrum-format Bitcoin address. 10 | # 11 | # To generate a document with a lot of addresses, try something like this: 12 | # 13 | # hexdump -v -e '/1 "%02X"' -n 32 /dev/urandom | ./format_key "%a:%w" 14 | 15 | import argparse 16 | import datetime 17 | from jinja2 import Template 18 | import sys 19 | 20 | TEX_TEMPLATE = r"""\documentclass[landscape, twocolumn]{book} 21 | \setlength\columnsep{\dimexpr 1in/2\relax} 22 | \setlength{\columnseprule}{0.4pt} 23 | \usepackage{pst-barcode} 24 | \usepackage{auto-pst-pdf} 25 | \usepackage[margin=0.6in]{geometry} 26 | 27 | \usepackage{fancyhdr} 28 | \pagestyle{fancy} 29 | \fancyhead{} 30 | \renewcommand{\headrulewidth}{0pt} 31 | \fancyfoot{} 32 | \fancyfoot[RE,LO]{Generated {{ today }}} 33 | 34 | \begin{document} 35 | 36 | \begin{enumerate} 37 | \setcounter{enumi}{0} 38 | {% for key in keys %}\item 39 | \begin{pspicture}(1in,1in) 40 | \psbarcode{%raw%}{{%endraw%}{{ key.address }}{%raw%}}{%endraw%}{eclevel=M width=0.8 height=0.8}{qrcode} 41 | \end{pspicture} 42 | {% if not exclude_private_keys %}\hfill 43 | \begin{pspicture}(1in,1in) 44 | \psbarcode{%raw%}{{%endraw%}{{ key.private_key }}{%raw%}}{%endraw%}{eclevel=H width=0.8 height=0.8}{qrcode} 45 | \end{pspicture} 46 | {% endif %} 47 | 48 | {{ key.address }} 49 | {% if not exclude_private_keys and not exclude_private_key_text %} 50 | {\footnotesize {{ key.private_key }} } 51 | {% endif %}{% if loop.index % 5 != 0 %}\hrule{% else %}\pagebreak 52 | {% endif %} 53 | {% endfor %} 54 | 55 | \end{enumerate} 56 | 57 | \end{document}""" 58 | 59 | HTML_TEMPLATE = r""" 60 | 61 | 62 | 79 | 80 | 81 | {% for key in keys %} 82 |
83 | {{ loop.index }}. 84 | {% if not exclude_private_keys %} 85 | 86 | 87 | 88 | {% endif %} 89 |
{{ key.address }} 90 | {% if not exclude_private_keys and not exclude_private_key_text %} 91 |
{{ key.private_key }} 92 | {% endif %} 93 |
94 | {% endfor %} 95 | 96 | 97 | """ 98 | 99 | def make_data_url(data, important=False): 100 | import StringIO 101 | import urllib 102 | import qrcode 103 | box_size=3 104 | if important: 105 | error_correction=qrcode.constants.ERROR_CORRECT_H 106 | else: 107 | error_correction=qrcode.constants.ERROR_CORRECT_M 108 | 109 | qr = qrcode.QRCode(version=1, 110 | box_size=box_size, 111 | error_correction=error_correction) 112 | qr.add_data(data) 113 | qr.make() 114 | img = qr.make_image() 115 | f = StringIO.StringIO() 116 | img.save(f, "PNG") 117 | return urllib.quote(f.getvalue()) 118 | 119 | def print_pages(args): 120 | if args.filename: 121 | f = open(args.filename) 122 | lines = f.readlines() 123 | f.close() 124 | else: 125 | lines = sys.stdin.readlines() 126 | if args.html: 127 | template = Template(HTML_TEMPLATE) 128 | else: 129 | template = Template(TEX_TEMPLATE) 130 | keys = [] 131 | for line in lines: 132 | key = {} 133 | values = line.split(":") 134 | address, private_key = values[0], values[1] 135 | if args.exclude_addresses: 136 | address = '[address hidden]' 137 | if args.elide_addresses: 138 | address = address[0] + "..." + address[-8:] 139 | (key['address'], key['private_key']) = address.strip(), private_key.strip() 140 | if args.html: 141 | key['address_data_url'] = make_data_url(address, False) 142 | key['private_key_data_url'] = make_data_url(private_key, True) 143 | keys.append(key) 144 | print template.render(keys=keys, 145 | today=datetime.date.today(), 146 | exclude_private_keys=args.exclude_private_keys, 147 | exclude_private_key_text=args.exclude_private_key_text) 148 | 149 | parser = argparse.ArgumentParser(description='Generates a TeX-format document ' + 150 | 'that nicely formats a Bitcoin paper wallet for offline storage.') 151 | parser.add_argument("filename", nargs='?', 152 | help="file of Electrum-format Bitcoin addresses to read. Otherwise reads from stdin.") 153 | parser.add_argument("-x", "--exclude-private-keys", action="store_true", 154 | help="excludes private keys from document") 155 | parser.add_argument("--exclude-private-key-text", action="store_true", 156 | help="excludes text representations of private keys from document") 157 | parser.add_argument("--html", action="store_true", 158 | help="generate HTML instead of TeX") 159 | parser.add_argument("-a", "--exclude-addresses", action="store_true", 160 | help="excludes addresses from document") 161 | parser.add_argument("-e", "--elide-addresses", action="store_true", 162 | help="includes only partial addresses") 163 | 164 | args = parser.parse_args() 165 | print_pages(args) 166 | -------------------------------------------------------------------------------- /ocraextended.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sowbug/cold-wallet-generator/600ac8bc29bac14b49a5ac29cf231d1fc3a0a59e/ocraextended.ttf -------------------------------------------------------------------------------- /sample_keys.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | 22 | 23 |
24 | 1. 25 | 26 | 27 | 28 | 29 | 30 |
1EekXLWi3FsUF2CfuKBcP4RkHKrzWEhtLD 31 | 32 |
5KDZiNR2hP5StNbb1NZY2ixrHW9fYZ5asw1GKgdU1sEyAveeN1L 33 | 34 |
35 | 36 |
37 | 2. 38 | 39 | 40 | 41 | 42 | 43 |
1Pobunf2pKjKte3ZKYtCuMnExQirFkh3Qh 44 | 45 |
5J4foJbEXwnBgckU1fYc57spR427aP1hGA1GHLXQBds3Ux9zt1P 46 | 47 |
48 | 49 |
50 | 3. 51 | 52 | 53 | 54 | 55 | 56 |
1386Cx7hYfyfEMBfiggwSxqgZyn1hSzqgY 57 | 58 |
5JPsoKmf2vAcRM5cB8xuTGV5aNuPJpF4Az7zyFP6SRStic2dBLd 59 | 60 |
61 | 62 |
63 | 4. 64 | 65 | 66 | 67 | 68 | 69 |
19GeuwoVAg3e8jCridEsEaDVpKDbQNGxRu 70 | 71 |
5HtpFXWSKhLERtX3A4RZ51F33FeGW5og2RWxCURmMXdqiwhZiwJ 72 | 73 |
74 | 75 |
76 | 5. 77 | 78 | 79 | 80 | 81 | 82 |
1DnfoTijL5WrQiSLAr3bsp9LVXGnrg59MS 83 | 84 |
5KBnSEb9p35GRonSkvviV6uTqX4zvBY7SriMsvPsbGvo5LJiZc8 85 | 86 |
87 | 88 |
89 | 6. 90 | 91 | 92 | 93 | 94 | 95 |
1NRUutZqqrwSswAwVzDfMKAmSsB6RjSQyQ 96 | 97 |
5JfDUTT4zMYrEoaMaFvptAyaJn9SuutcgsxRMd2LnRkGWptGjJs 98 | 99 |
100 | 101 |
102 | 7. 103 | 104 | 105 | 106 | 107 | 108 |
1NrG2uHbGNuGW2XZWra2cnaycvBurQXAqT 109 | 110 |
5JAbJa6FZtMZHjkmjrNrVD3EK7TDBip3F4WVCBknucwKtS8MGWH 111 | 112 |
113 | 114 |
115 | 8. 116 | 117 | 118 | 119 | 120 | 121 |
1XSDvNDgpZi96vRGgRUsmQkde1EjhnicX 122 | 123 |
5J5egoxHUXBSWbboWgDX7NjsNJq5uFxwqWp6GbJpwmYF1eLVqds 124 | 125 |
126 | 127 |
128 | 9. 129 | 130 | 131 | 132 | 133 | 134 |
15oQ3fFk8ui4uvGHGci1D11quwdgGBU1Pz 135 | 136 |
5JBDFzWpyH59HaPKAhCJPWYywT7xt3ubctjYDGurvsZ7RKNgtM5 137 | 138 |
139 | 140 |
141 | 10. 142 | 143 | 144 | 145 | 146 | 147 |
17sRz8NYLWRj2tDTWhuHR3bCmbkvuCFkuV 148 | 149 |
5JtxUQ7onFjgZ4FhCdZ2Fgir8yoeAQRJqMu9V7pfUvNoja6z993 150 | 151 |
152 | 153 |
154 | 11. 155 | 156 | 157 | 158 | 159 | 160 |
19wJ8bMM2ZsBUGDiKmLiZ3w5wqZsTSv93V 161 | 162 |
5JMQvaraDkYskLDEtW17UTDbyC2zfVsn1TKzUbNHHsF7RQFewwR 163 | 164 |
165 | 166 |
167 | 12. 168 | 169 | 170 | 171 | 172 | 173 |
13cYKE7hPj8kScW9Voo9463WxTAZFx1xcY 174 | 175 |
5Jv46eS8QbrGfZRnGQ2Lrqz6xnYyTFN3ME8JpHgPsESPpNWo8tX 176 | 177 |
178 | 179 |
180 | 13. 181 | 182 | 183 | 184 | 185 | 186 |
1K54UR4Bkwog93fr9rt3JQ3XBpU1Yt63qV 187 | 188 |
5Jk2x9vupG8LGNJEZypSnNS4hLuu1TxUJsxn2vVo3GYW7MWSf6N 189 | 190 |
191 | 192 |
193 | 14. 194 | 195 | 196 | 197 | 198 | 199 |
1EA3igjfGhCD3Hn8E7VcaQJjnWsPQRbeRW 200 | 201 |
5J2A42cZwbhxT9swCvWaPjjJX7HsVx3tr3RHn7uFZ9ZARANSjLN 202 | 203 |
204 | 205 |
206 | 15. 207 | 208 | 209 | 210 | 211 | 212 |
1E7h4EkdThti7xXoGK1G3QTwd8YVnzSaad 213 | 214 |
5KMAczhMPb21ud1iXDadeDvzfUQMXjDWbTcG6yBoGzaEih6hpHA 215 | 216 |
217 | 218 |
219 | 16. 220 | 221 | 222 | 223 | 224 | 225 |
1HAT2gwaXpfAkfdLRW9yovRo79bjJc87kF 226 | 227 |
5JzhNyZuuaZsezm6p274VxanFWGfiS3WXs4KN8iqPfLMhZXRju4 228 | 229 |
230 | 231 |
232 | 17. 233 | 234 | 235 | 236 | 237 | 238 |
1DS2aA4buXFbNiroLh3Syoe6mLyezRaDwW 239 | 240 |
5KLpxEVrV6jdhCtDWnGeAgBLsFGU8aWwhvEty3jz7wqwgHhb2V5 241 | 242 |
243 | 244 |
245 | 18. 246 | 247 | 248 | 249 | 250 | 251 |
15hBv522HPuNzRu3dEy4u2RvyAL363TLQc 252 | 253 |
5KentEWDCci7zgCn7Z2q4PnY9kEC41se2vU6yCRy5ufD2mXCsQf 254 | 255 |
256 | 257 |
258 | 19. 259 | 260 | 261 | 262 | 263 | 264 |
1HMaXoBEPZFzvL4DSZy1keR1YzZBg1NJJU 265 | 266 |
5JnL5mK1UwGVYyjPxqBaAj2HmBsgfbAyrKGrmUWHh5GJ9zGGHKn 267 | 268 |
269 | 270 |
271 | 20. 272 | 273 | 274 | 275 | 276 | 277 |
16DoDT9RVn8KuWgW27QkCzeaRL3D1pQLqX 278 | 279 |
5Kjseuzx2AFGUY4md9gXpxM6Hgfek5EaawTSaX7EjE51XVUzAEX 280 | 281 |
282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /sample_keys.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sowbug/cold-wallet-generator/600ac8bc29bac14b49a5ac29cf231d1fc3a0a59e/sample_keys.pdf -------------------------------------------------------------------------------- /sample_keys.tex: -------------------------------------------------------------------------------- 1 | \documentclass[landscape, twocolumn]{book} 2 | \setlength\columnsep{\dimexpr 1in/2\relax} 3 | \setlength{\columnseprule}{0.4pt} 4 | \usepackage{pst-barcode} 5 | \usepackage{auto-pst-pdf} 6 | \usepackage[margin=0.5in]{geometry} 7 | 8 | \usepackage{fancyhdr} 9 | \pagestyle{fancy} 10 | \fancyhead{} 11 | \renewcommand{\headrulewidth}{0pt} 12 | \fancyfoot{} 13 | \fancyfoot[RE,LO]{Generated 2013-07-21} 14 | 15 | \begin{document} 16 | 17 | \begin{enumerate} 18 | \setcounter{enumi}{0} 19 | \item 20 | \begin{pspicture}(1in,1in) 21 | \psbarcode{1EekXLWi3FsUF2CfuKBcP4RkHKrzWEhtLD}{eclevel=M width=0.8 height=0.8}{qrcode} 22 | \end{pspicture} 23 | \hfill 24 | \begin{pspicture}(1in,1in) 25 | \psbarcode{5KDZiNR2hP5StNbb1NZY2ixrHW9fYZ5asw1GKgdU1sEyAveeN1L}{eclevel=H width=0.8 height=0.8}{qrcode} 26 | \end{pspicture} 27 | 28 | 29 | 1EekXLWi3FsUF2CfuKBcP4RkHKrzWEhtLD 30 | 31 | 5KDZiNR2hP5StNbb1NZY2ixrHW9fYZ5asw1GKgdU1sEyAveeN1L 32 | \hrule 33 | \item 34 | \begin{pspicture}(1in,1in) 35 | \psbarcode{1Pobunf2pKjKte3ZKYtCuMnExQirFkh3Qh}{eclevel=M width=0.8 height=0.8}{qrcode} 36 | \end{pspicture} 37 | \hfill 38 | \begin{pspicture}(1in,1in) 39 | \psbarcode{5J4foJbEXwnBgckU1fYc57spR427aP1hGA1GHLXQBds3Ux9zt1P}{eclevel=H width=0.8 height=0.8}{qrcode} 40 | \end{pspicture} 41 | 42 | 43 | 1Pobunf2pKjKte3ZKYtCuMnExQirFkh3Qh 44 | 45 | 5J4foJbEXwnBgckU1fYc57spR427aP1hGA1GHLXQBds3Ux9zt1P 46 | \hrule 47 | \item 48 | \begin{pspicture}(1in,1in) 49 | \psbarcode{1386Cx7hYfyfEMBfiggwSxqgZyn1hSzqgY}{eclevel=M width=0.8 height=0.8}{qrcode} 50 | \end{pspicture} 51 | \hfill 52 | \begin{pspicture}(1in,1in) 53 | \psbarcode{5JPsoKmf2vAcRM5cB8xuTGV5aNuPJpF4Az7zyFP6SRStic2dBLd}{eclevel=H width=0.8 height=0.8}{qrcode} 54 | \end{pspicture} 55 | 56 | 57 | 1386Cx7hYfyfEMBfiggwSxqgZyn1hSzqgY 58 | 59 | 5JPsoKmf2vAcRM5cB8xuTGV5aNuPJpF4Az7zyFP6SRStic2dBLd 60 | \hrule 61 | \item 62 | \begin{pspicture}(1in,1in) 63 | \psbarcode{19GeuwoVAg3e8jCridEsEaDVpKDbQNGxRu}{eclevel=M width=0.8 height=0.8}{qrcode} 64 | \end{pspicture} 65 | \hfill 66 | \begin{pspicture}(1in,1in) 67 | \psbarcode{5HtpFXWSKhLERtX3A4RZ51F33FeGW5og2RWxCURmMXdqiwhZiwJ}{eclevel=H width=0.8 height=0.8}{qrcode} 68 | \end{pspicture} 69 | 70 | 71 | 19GeuwoVAg3e8jCridEsEaDVpKDbQNGxRu 72 | 73 | 5HtpFXWSKhLERtX3A4RZ51F33FeGW5og2RWxCURmMXdqiwhZiwJ 74 | \hrule 75 | \item 76 | \begin{pspicture}(1in,1in) 77 | \psbarcode{1DnfoTijL5WrQiSLAr3bsp9LVXGnrg59MS}{eclevel=M width=0.8 height=0.8}{qrcode} 78 | \end{pspicture} 79 | \hfill 80 | \begin{pspicture}(1in,1in) 81 | \psbarcode{5KBnSEb9p35GRonSkvviV6uTqX4zvBY7SriMsvPsbGvo5LJiZc8}{eclevel=H width=0.8 height=0.8}{qrcode} 82 | \end{pspicture} 83 | 84 | 85 | 1DnfoTijL5WrQiSLAr3bsp9LVXGnrg59MS 86 | 87 | 5KBnSEb9p35GRonSkvviV6uTqX4zvBY7SriMsvPsbGvo5LJiZc8 88 | \pagebreak 89 | 90 | \item 91 | \begin{pspicture}(1in,1in) 92 | \psbarcode{1NRUutZqqrwSswAwVzDfMKAmSsB6RjSQyQ}{eclevel=M width=0.8 height=0.8}{qrcode} 93 | \end{pspicture} 94 | \hfill 95 | \begin{pspicture}(1in,1in) 96 | \psbarcode{5JfDUTT4zMYrEoaMaFvptAyaJn9SuutcgsxRMd2LnRkGWptGjJs}{eclevel=H width=0.8 height=0.8}{qrcode} 97 | \end{pspicture} 98 | 99 | 100 | 1NRUutZqqrwSswAwVzDfMKAmSsB6RjSQyQ 101 | 102 | 5JfDUTT4zMYrEoaMaFvptAyaJn9SuutcgsxRMd2LnRkGWptGjJs 103 | \hrule 104 | \item 105 | \begin{pspicture}(1in,1in) 106 | \psbarcode{1NrG2uHbGNuGW2XZWra2cnaycvBurQXAqT}{eclevel=M width=0.8 height=0.8}{qrcode} 107 | \end{pspicture} 108 | \hfill 109 | \begin{pspicture}(1in,1in) 110 | \psbarcode{5JAbJa6FZtMZHjkmjrNrVD3EK7TDBip3F4WVCBknucwKtS8MGWH}{eclevel=H width=0.8 height=0.8}{qrcode} 111 | \end{pspicture} 112 | 113 | 114 | 1NrG2uHbGNuGW2XZWra2cnaycvBurQXAqT 115 | 116 | 5JAbJa6FZtMZHjkmjrNrVD3EK7TDBip3F4WVCBknucwKtS8MGWH 117 | \hrule 118 | \item 119 | \begin{pspicture}(1in,1in) 120 | \psbarcode{1XSDvNDgpZi96vRGgRUsmQkde1EjhnicX}{eclevel=M width=0.8 height=0.8}{qrcode} 121 | \end{pspicture} 122 | \hfill 123 | \begin{pspicture}(1in,1in) 124 | \psbarcode{5J5egoxHUXBSWbboWgDX7NjsNJq5uFxwqWp6GbJpwmYF1eLVqds}{eclevel=H width=0.8 height=0.8}{qrcode} 125 | \end{pspicture} 126 | 127 | 128 | 1XSDvNDgpZi96vRGgRUsmQkde1EjhnicX 129 | 130 | 5J5egoxHUXBSWbboWgDX7NjsNJq5uFxwqWp6GbJpwmYF1eLVqds 131 | \hrule 132 | \item 133 | \begin{pspicture}(1in,1in) 134 | \psbarcode{15oQ3fFk8ui4uvGHGci1D11quwdgGBU1Pz}{eclevel=M width=0.8 height=0.8}{qrcode} 135 | \end{pspicture} 136 | \hfill 137 | \begin{pspicture}(1in,1in) 138 | \psbarcode{5JBDFzWpyH59HaPKAhCJPWYywT7xt3ubctjYDGurvsZ7RKNgtM5}{eclevel=H width=0.8 height=0.8}{qrcode} 139 | \end{pspicture} 140 | 141 | 142 | 15oQ3fFk8ui4uvGHGci1D11quwdgGBU1Pz 143 | 144 | 5JBDFzWpyH59HaPKAhCJPWYywT7xt3ubctjYDGurvsZ7RKNgtM5 145 | \hrule 146 | \item 147 | \begin{pspicture}(1in,1in) 148 | \psbarcode{17sRz8NYLWRj2tDTWhuHR3bCmbkvuCFkuV}{eclevel=M width=0.8 height=0.8}{qrcode} 149 | \end{pspicture} 150 | \hfill 151 | \begin{pspicture}(1in,1in) 152 | \psbarcode{5JtxUQ7onFjgZ4FhCdZ2Fgir8yoeAQRJqMu9V7pfUvNoja6z993}{eclevel=H width=0.8 height=0.8}{qrcode} 153 | \end{pspicture} 154 | 155 | 156 | 17sRz8NYLWRj2tDTWhuHR3bCmbkvuCFkuV 157 | 158 | 5JtxUQ7onFjgZ4FhCdZ2Fgir8yoeAQRJqMu9V7pfUvNoja6z993 159 | \pagebreak 160 | 161 | \item 162 | \begin{pspicture}(1in,1in) 163 | \psbarcode{19wJ8bMM2ZsBUGDiKmLiZ3w5wqZsTSv93V}{eclevel=M width=0.8 height=0.8}{qrcode} 164 | \end{pspicture} 165 | \hfill 166 | \begin{pspicture}(1in,1in) 167 | \psbarcode{5JMQvaraDkYskLDEtW17UTDbyC2zfVsn1TKzUbNHHsF7RQFewwR}{eclevel=H width=0.8 height=0.8}{qrcode} 168 | \end{pspicture} 169 | 170 | 171 | 19wJ8bMM2ZsBUGDiKmLiZ3w5wqZsTSv93V 172 | 173 | 5JMQvaraDkYskLDEtW17UTDbyC2zfVsn1TKzUbNHHsF7RQFewwR 174 | \hrule 175 | \item 176 | \begin{pspicture}(1in,1in) 177 | \psbarcode{13cYKE7hPj8kScW9Voo9463WxTAZFx1xcY}{eclevel=M width=0.8 height=0.8}{qrcode} 178 | \end{pspicture} 179 | \hfill 180 | \begin{pspicture}(1in,1in) 181 | \psbarcode{5Jv46eS8QbrGfZRnGQ2Lrqz6xnYyTFN3ME8JpHgPsESPpNWo8tX}{eclevel=H width=0.8 height=0.8}{qrcode} 182 | \end{pspicture} 183 | 184 | 185 | 13cYKE7hPj8kScW9Voo9463WxTAZFx1xcY 186 | 187 | 5Jv46eS8QbrGfZRnGQ2Lrqz6xnYyTFN3ME8JpHgPsESPpNWo8tX 188 | \hrule 189 | \item 190 | \begin{pspicture}(1in,1in) 191 | \psbarcode{1K54UR4Bkwog93fr9rt3JQ3XBpU1Yt63qV}{eclevel=M width=0.8 height=0.8}{qrcode} 192 | \end{pspicture} 193 | \hfill 194 | \begin{pspicture}(1in,1in) 195 | \psbarcode{5Jk2x9vupG8LGNJEZypSnNS4hLuu1TxUJsxn2vVo3GYW7MWSf6N}{eclevel=H width=0.8 height=0.8}{qrcode} 196 | \end{pspicture} 197 | 198 | 199 | 1K54UR4Bkwog93fr9rt3JQ3XBpU1Yt63qV 200 | 201 | 5Jk2x9vupG8LGNJEZypSnNS4hLuu1TxUJsxn2vVo3GYW7MWSf6N 202 | \hrule 203 | \item 204 | \begin{pspicture}(1in,1in) 205 | \psbarcode{1EA3igjfGhCD3Hn8E7VcaQJjnWsPQRbeRW}{eclevel=M width=0.8 height=0.8}{qrcode} 206 | \end{pspicture} 207 | \hfill 208 | \begin{pspicture}(1in,1in) 209 | \psbarcode{5J2A42cZwbhxT9swCvWaPjjJX7HsVx3tr3RHn7uFZ9ZARANSjLN}{eclevel=H width=0.8 height=0.8}{qrcode} 210 | \end{pspicture} 211 | 212 | 213 | 1EA3igjfGhCD3Hn8E7VcaQJjnWsPQRbeRW 214 | 215 | 5J2A42cZwbhxT9swCvWaPjjJX7HsVx3tr3RHn7uFZ9ZARANSjLN 216 | \hrule 217 | \item 218 | \begin{pspicture}(1in,1in) 219 | \psbarcode{1E7h4EkdThti7xXoGK1G3QTwd8YVnzSaad}{eclevel=M width=0.8 height=0.8}{qrcode} 220 | \end{pspicture} 221 | \hfill 222 | \begin{pspicture}(1in,1in) 223 | \psbarcode{5KMAczhMPb21ud1iXDadeDvzfUQMXjDWbTcG6yBoGzaEih6hpHA}{eclevel=H width=0.8 height=0.8}{qrcode} 224 | \end{pspicture} 225 | 226 | 227 | 1E7h4EkdThti7xXoGK1G3QTwd8YVnzSaad 228 | 229 | 5KMAczhMPb21ud1iXDadeDvzfUQMXjDWbTcG6yBoGzaEih6hpHA 230 | \pagebreak 231 | 232 | \item 233 | \begin{pspicture}(1in,1in) 234 | \psbarcode{1HAT2gwaXpfAkfdLRW9yovRo79bjJc87kF}{eclevel=M width=0.8 height=0.8}{qrcode} 235 | \end{pspicture} 236 | \hfill 237 | \begin{pspicture}(1in,1in) 238 | \psbarcode{5JzhNyZuuaZsezm6p274VxanFWGfiS3WXs4KN8iqPfLMhZXRju4}{eclevel=H width=0.8 height=0.8}{qrcode} 239 | \end{pspicture} 240 | 241 | 242 | 1HAT2gwaXpfAkfdLRW9yovRo79bjJc87kF 243 | 244 | 5JzhNyZuuaZsezm6p274VxanFWGfiS3WXs4KN8iqPfLMhZXRju4 245 | \hrule 246 | \item 247 | \begin{pspicture}(1in,1in) 248 | \psbarcode{1DS2aA4buXFbNiroLh3Syoe6mLyezRaDwW}{eclevel=M width=0.8 height=0.8}{qrcode} 249 | \end{pspicture} 250 | \hfill 251 | \begin{pspicture}(1in,1in) 252 | \psbarcode{5KLpxEVrV6jdhCtDWnGeAgBLsFGU8aWwhvEty3jz7wqwgHhb2V5}{eclevel=H width=0.8 height=0.8}{qrcode} 253 | \end{pspicture} 254 | 255 | 256 | 1DS2aA4buXFbNiroLh3Syoe6mLyezRaDwW 257 | 258 | 5KLpxEVrV6jdhCtDWnGeAgBLsFGU8aWwhvEty3jz7wqwgHhb2V5 259 | \hrule 260 | \item 261 | \begin{pspicture}(1in,1in) 262 | \psbarcode{15hBv522HPuNzRu3dEy4u2RvyAL363TLQc}{eclevel=M width=0.8 height=0.8}{qrcode} 263 | \end{pspicture} 264 | \hfill 265 | \begin{pspicture}(1in,1in) 266 | \psbarcode{5KentEWDCci7zgCn7Z2q4PnY9kEC41se2vU6yCRy5ufD2mXCsQf}{eclevel=H width=0.8 height=0.8}{qrcode} 267 | \end{pspicture} 268 | 269 | 270 | 15hBv522HPuNzRu3dEy4u2RvyAL363TLQc 271 | 272 | 5KentEWDCci7zgCn7Z2q4PnY9kEC41se2vU6yCRy5ufD2mXCsQf 273 | \hrule 274 | \item 275 | \begin{pspicture}(1in,1in) 276 | \psbarcode{1HMaXoBEPZFzvL4DSZy1keR1YzZBg1NJJU}{eclevel=M width=0.8 height=0.8}{qrcode} 277 | \end{pspicture} 278 | \hfill 279 | \begin{pspicture}(1in,1in) 280 | \psbarcode{5JnL5mK1UwGVYyjPxqBaAj2HmBsgfbAyrKGrmUWHh5GJ9zGGHKn}{eclevel=H width=0.8 height=0.8}{qrcode} 281 | \end{pspicture} 282 | 283 | 284 | 1HMaXoBEPZFzvL4DSZy1keR1YzZBg1NJJU 285 | 286 | 5JnL5mK1UwGVYyjPxqBaAj2HmBsgfbAyrKGrmUWHh5GJ9zGGHKn 287 | \hrule 288 | \item 289 | \begin{pspicture}(1in,1in) 290 | \psbarcode{16DoDT9RVn8KuWgW27QkCzeaRL3D1pQLqX}{eclevel=M width=0.8 height=0.8}{qrcode} 291 | \end{pspicture} 292 | \hfill 293 | \begin{pspicture}(1in,1in) 294 | \psbarcode{5Kjseuzx2AFGUY4md9gXpxM6Hgfek5EaawTSaX7EjE51XVUzAEX}{eclevel=H width=0.8 height=0.8}{qrcode} 295 | \end{pspicture} 296 | 297 | 298 | 16DoDT9RVn8KuWgW27QkCzeaRL3D1pQLqX 299 | 300 | 5Kjseuzx2AFGUY4md9gXpxM6Hgfek5EaawTSaX7EjE51XVUzAEX 301 | \pagebreak 302 | 303 | 304 | 305 | \end{enumerate} 306 | 307 | \end{document} 308 | -------------------------------------------------------------------------------- /sample_keys.txt: -------------------------------------------------------------------------------- 1 | 1EekXLWi3FsUF2CfuKBcP4RkHKrzWEhtLD:5KDZiNR2hP5StNbb1NZY2ixrHW9fYZ5asw1GKgdU1sEyAveeN1L 2 | 1Pobunf2pKjKte3ZKYtCuMnExQirFkh3Qh:5J4foJbEXwnBgckU1fYc57spR427aP1hGA1GHLXQBds3Ux9zt1P 3 | 1386Cx7hYfyfEMBfiggwSxqgZyn1hSzqgY:5JPsoKmf2vAcRM5cB8xuTGV5aNuPJpF4Az7zyFP6SRStic2dBLd 4 | 19GeuwoVAg3e8jCridEsEaDVpKDbQNGxRu:5HtpFXWSKhLERtX3A4RZ51F33FeGW5og2RWxCURmMXdqiwhZiwJ 5 | 1DnfoTijL5WrQiSLAr3bsp9LVXGnrg59MS:5KBnSEb9p35GRonSkvviV6uTqX4zvBY7SriMsvPsbGvo5LJiZc8 6 | 1NRUutZqqrwSswAwVzDfMKAmSsB6RjSQyQ:5JfDUTT4zMYrEoaMaFvptAyaJn9SuutcgsxRMd2LnRkGWptGjJs 7 | 1NrG2uHbGNuGW2XZWra2cnaycvBurQXAqT:5JAbJa6FZtMZHjkmjrNrVD3EK7TDBip3F4WVCBknucwKtS8MGWH 8 | 1XSDvNDgpZi96vRGgRUsmQkde1EjhnicX:5J5egoxHUXBSWbboWgDX7NjsNJq5uFxwqWp6GbJpwmYF1eLVqds 9 | 15oQ3fFk8ui4uvGHGci1D11quwdgGBU1Pz:5JBDFzWpyH59HaPKAhCJPWYywT7xt3ubctjYDGurvsZ7RKNgtM5 10 | 17sRz8NYLWRj2tDTWhuHR3bCmbkvuCFkuV:5JtxUQ7onFjgZ4FhCdZ2Fgir8yoeAQRJqMu9V7pfUvNoja6z993 11 | 19wJ8bMM2ZsBUGDiKmLiZ3w5wqZsTSv93V:5JMQvaraDkYskLDEtW17UTDbyC2zfVsn1TKzUbNHHsF7RQFewwR 12 | 13cYKE7hPj8kScW9Voo9463WxTAZFx1xcY:5Jv46eS8QbrGfZRnGQ2Lrqz6xnYyTFN3ME8JpHgPsESPpNWo8tX 13 | 1K54UR4Bkwog93fr9rt3JQ3XBpU1Yt63qV:5Jk2x9vupG8LGNJEZypSnNS4hLuu1TxUJsxn2vVo3GYW7MWSf6N 14 | 1EA3igjfGhCD3Hn8E7VcaQJjnWsPQRbeRW:5J2A42cZwbhxT9swCvWaPjjJX7HsVx3tr3RHn7uFZ9ZARANSjLN 15 | 1E7h4EkdThti7xXoGK1G3QTwd8YVnzSaad:5KMAczhMPb21ud1iXDadeDvzfUQMXjDWbTcG6yBoGzaEih6hpHA 16 | 1HAT2gwaXpfAkfdLRW9yovRo79bjJc87kF:5JzhNyZuuaZsezm6p274VxanFWGfiS3WXs4KN8iqPfLMhZXRju4 17 | 1DS2aA4buXFbNiroLh3Syoe6mLyezRaDwW:5KLpxEVrV6jdhCtDWnGeAgBLsFGU8aWwhvEty3jz7wqwgHhb2V5 18 | 15hBv522HPuNzRu3dEy4u2RvyAL363TLQc:5KentEWDCci7zgCn7Z2q4PnY9kEC41se2vU6yCRy5ufD2mXCsQf 19 | 1HMaXoBEPZFzvL4DSZy1keR1YzZBg1NJJU:5JnL5mK1UwGVYyjPxqBaAj2HmBsgfbAyrKGrmUWHh5GJ9zGGHKn 20 | 16DoDT9RVn8KuWgW27QkCzeaRL3D1pQLqX:5Kjseuzx2AFGUY4md9gXpxM6Hgfek5EaawTSaX7EjE51XVUzAEX 21 | --------------------------------------------------------------------------------