├── .gitignore ├── README ├── COPYING ├── example.py ├── blktemplate.py └── blkmaker.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyo 2 | *.pyc 3 | *~ 4 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | For usage, check out example.py 2 | 3 | Also note that you should NOT roll ntime for data retrieved without explicitly 4 | checking that it falls within the template's limitations (mintime, maxtime, 5 | mintimeoff, and maxtimeoff); read the BIP 23 specification in detail to 6 | understand how they work. It is usually best to simply get more data as often 7 | as it is needed. For blkmk_get_mdata, you may specify that you intend to roll 8 | the ntime header exactly once per second past usetime - it will then set 9 | *out_expires such that the expiration occurs before you roll beyond any ntime 10 | limits. If you are rolling ntime at any rate other than once per second, you 11 | should NOT specify can_roll_ntime to blkmk_get_mdata, and must check that your 12 | usage falls within the explicit template limits yourself. 13 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2012-2014 Luke Dashjr 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2012-2014 Luke Dashjr 3 | # 4 | # This program is free software; you can redistribute it and/or modify it 5 | # under the terms of the standard MIT license. See COPYING for more details. 6 | 7 | from blkmaker import _dblsha256 8 | import blktemplate 9 | import json 10 | import struct 11 | import sys 12 | 13 | # This test_input data is released under the terms of the Creative Commons "CC0 1.0 Universal" license and/or copyright waiver. 14 | test_input = ''' 15 | { 16 | "result": { 17 | "previousblockhash": "000000004d424dec1c660a68456b8271d09628a80cc62583e5904f5894a2483c", 18 | "target": "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 19 | "noncerange": "00000000ffffffff", 20 | "transactions": [], 21 | "sigoplimit": 20000, 22 | "expires": 120, 23 | "longpoll": "/LP", 24 | "height": 23957, 25 | "coinbasetxn": { 26 | "data": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1302955d0f00456c6967697573005047dc66085fffffffff02fff1052a010000001976a9144ebeb1cd26d6227635828d60d3e0ed7d0da248fb88ac01000000000000001976a9147c866aee1fa2f3b3d5effad576df3dbf1f07475588ac00000000" 27 | }, 28 | "version": 2, 29 | "curtime": 1346886758, 30 | "mutable": ["coinbase/append"], 31 | "sizelimit": 1000000, 32 | "bits": "ffff001d" 33 | }, 34 | "id": 0, 35 | "error": null 36 | } 37 | ''' 38 | 39 | def send_json(req): 40 | print(json.dumps(req, indent=4)) 41 | 42 | tmpl = blktemplate.Template() 43 | req = tmpl.request() 44 | 45 | # send req to server and parse response into req 46 | send_json(req) 47 | if (len(sys.argv) == 2): 48 | req = json.load(sys.stdin) 49 | else: 50 | req = json.loads(test_input) 51 | send_json(req) 52 | 53 | try: 54 | # Bypass Python 3 idiocy 55 | range = xrange 56 | except: 57 | pass 58 | 59 | tmpl.add(req) 60 | while (tmpl.time_left() and tmpl.work_left()): 61 | (data, dataid) = tmpl.get_data() 62 | assert(len(data) >= 76) 63 | 64 | # mine the right nonce 65 | for nonce in range(0x7fffffff): 66 | data = data[:76] + struct.pack('!I', nonce) 67 | blkhash = _dblsha256(data) 68 | if blkhash[28:] == b'\0\0\0\0': 69 | break 70 | if (not (nonce % 0x1000)): 71 | sys.stdout.write("0x%8x hashes done...\r" % nonce) 72 | sys.stdout.flush() 73 | print("Found nonce: 0x%8x \n" % nonce) 74 | 75 | req = tmpl.submit(data, dataid, nonce) 76 | # send req to server 77 | send_json(req) 78 | -------------------------------------------------------------------------------- /blktemplate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012-2016 Luke Dashjr 2 | # 3 | # This program is free software; you can redistribute it and/or modify it 4 | # under the terms of the standard MIT license. See COPYING for more details. 5 | 6 | from binascii import a2b_hex as __a2b_hex 7 | import blkmaker as _blkmaker 8 | from time import time as _time 9 | 10 | try: 11 | __a2b_hex('aa') 12 | _a2b_hex = __a2b_hex 13 | except TypeError: 14 | def _a2b_hex(a): 15 | return __a2b_hex(a.encode('ascii')) 16 | 17 | def request(jcaps, lpid = None): 18 | params = { 19 | 'capabilities': jcaps, 20 | 'maxversion': _blkmaker.MAX_BLOCK_VERSION, 21 | } 22 | if lpid: 23 | params['longpollid'] = lpid 24 | req = { 25 | 'id':0, 26 | 'method': 'getblocktemplate', 27 | 'params': [params], 28 | } 29 | return req 30 | 31 | class _Transaction: 32 | def __init__(self, txnj = {}): 33 | if txnj is None: 34 | return 35 | if 'data' not in txnj: 36 | raise ValueError("Missing or invalid type for transaction data") 37 | self.data = _a2b_hex(txnj['data']) 38 | 39 | class _LPInfo: 40 | pass 41 | 42 | class Template: 43 | def __init__(self): 44 | self.auxs = {} 45 | self.sigoplimit = 0xffff 46 | self.sizelimit = 0xffffffff 47 | self.maxtime = 0xffffffff 48 | self.maxtimeoff = 0x7fff 49 | self.mintime = 0 50 | self.mintimeoff = -0x7fff 51 | self.maxnonce = 0xffffffff 52 | self.expires = 0x7fff 53 | self.cbtxn = None 54 | self.next_dataid = 0 55 | self.version = None 56 | 57 | def addcaps(self): 58 | # TODO: make this a lot more flexible for merging 59 | # For now, it's a simple "filled" vs "not filled" 60 | if self.version: 61 | return 0 62 | return ('coinbasetxn', 'workid', 'time/increment', 'coinbase/append', 'version/force', 'version/reduce', 'submit/coinbase', 'submit/truncate') 63 | 64 | def get_longpoll(self): 65 | return self.lp 66 | 67 | def get_submitold(self): 68 | return self.submitold 69 | 70 | # Wrappers around blkmaker, for OO friendliness 71 | def init_generation3(self, script, override_cb=False): 72 | return _blkmaker.init_generation3(self, script, override_cb) 73 | def init_generation2(self, script, override_cb=False): 74 | return _blkmaker.init_generation2(self, script, override_cb) 75 | def init_generation(self, script, override_cb=False): 76 | return _blkmaker.init_generation(self, script, override_cb) 77 | def append_coinbase_safe2(self, append, extranoncesz = 0, merkle_only = False): 78 | return _blkmaker.append_coinbase_safe2(self, append, extranoncesz, merkle_only) 79 | def append_coinbase_safe(self, append, extranoncesz = 0, merkle_only = False): 80 | return _blkmaker.append_coinbase_safe(self, append, extranoncesz, merkle_only) 81 | def get_data(self, usetime = None): 82 | return _blkmaker.get_data(self, usetime) 83 | def get_mdata(self, usetime = None, out_expire = None, extranoncesz = _blkmaker.sizeof_workid): 84 | return _blkmaker.get_mdata(self, usetime, out_expire, extranoncesz) 85 | def time_left(self, nowtime = None): 86 | return _blkmaker.time_left(self, nowtime) 87 | def work_left(self): 88 | return _blkmaker.work_left(self) 89 | def propose(self, caps, foreign): 90 | return _blkmaker.propose(self, caps, foreign) 91 | def submit(self, data, dataid, nonce, foreign=False): 92 | return _blkmaker.submit(self, data, dataid, nonce, foreign) 93 | def submit_foreign(self, data, dataid, nonce): 94 | return _blkmaker.submit_foreign(self, data, dataid, nonce) 95 | 96 | # JSON-specific stuff 97 | def request(self, lpid = None): 98 | return request(self.addcaps(), lpid) 99 | 100 | def add(self, json, time_rcvd = None): 101 | if time_rcvd is None: time_rcvd = _time() 102 | if self.version: 103 | raise ValueError("Template already populated (combining not supported)") 104 | 105 | if 'result' in json: 106 | if json.get('error', None): 107 | raise ValueError('JSON result is error') 108 | json = json['result'] 109 | 110 | self.diffbits = _a2b_hex(json['bits'])[::-1] 111 | self.curtime = json['curtime'] 112 | self.height = json['height'] 113 | self.prevblk = _a2b_hex(json['previousblockhash'])[::-1] 114 | self.sigoplimit = json.get('sigoplimit', self.sigoplimit) 115 | self.sizelimit = json.get('sizelimit', self.sizelimit) 116 | self.version = json['version'] 117 | 118 | self.cbvalue = json.get('coinbasevalue', None) 119 | self.workid = json.get('workid', None) 120 | 121 | self.expires = json.get('expires', self.expires) 122 | self.maxtime = json.get('maxtime', self.maxtime) 123 | self.maxtimeoff = json.get('maxtimeoff', self.maxtimeoff) 124 | self.mintime = json.get('mintime', self.mintime) 125 | self.mintimeoff = json.get('mintimeoff', self.mintimeoff) 126 | 127 | self.lp = _LPInfo() 128 | if 'longpollid' in json: 129 | self.lp.lpid = json['longpollid'] 130 | self.lp.uri = json.get('longpolluri', None) 131 | self.submitold = json.get('submitold', True) 132 | 133 | self.txns = [] 134 | self.txns_datasz = 0 135 | for t in json['transactions']: 136 | tobj = _Transaction(t) 137 | self.txns.append(tobj) 138 | self.txns_datasz += len(tobj.data) 139 | 140 | if 'coinbasetxn' in json: 141 | self.cbtxn = _Transaction(json['coinbasetxn']) 142 | 143 | if 'coinbaseaux' in json: 144 | for aux in json['coinbaseaux']: 145 | self.auxs[aux] = _a2b_hex(json['coinbaseaux'][aux]) 146 | 147 | if 'target' in json: 148 | self.target = _a2b_hex(json['target']) 149 | 150 | self.mutations = set(json.get('mutable', ())) 151 | 152 | if (self.version > _blkmaker.MAX_BLOCK_VERSION or (self.version >= 2 and not self.height)): 153 | if 'version/reduce' in self.mutations: 154 | self.version = _blkmaker.MAX_BLOCK_VERSION if self.height else 1 155 | elif 'version/force' not in self.mutations: 156 | raise ValueError("Unrecognized block version, and not allowed to reduce or force it") 157 | 158 | self._time_rcvd = time_rcvd; 159 | 160 | return True 161 | -------------------------------------------------------------------------------- /blkmaker.py: -------------------------------------------------------------------------------- 1 | # Copyright 2012-2016 Luke Dashjr 2 | # 3 | # This program is free software; you can redistribute it and/or modify it 4 | # under the terms of the standard MIT license. See COPYING for more details. 5 | 6 | sizeof_workid = 8 7 | 8 | import base58 as _base58 9 | from binascii import b2a_hex as _b2a_hex 10 | from hashlib import sha256 as _sha256 11 | from struct import pack as _pack 12 | from time import time as _time 13 | 14 | from blktemplate import _Transaction, request as _request 15 | 16 | MAX_BLOCK_VERSION = 4 17 | 18 | coinbase_size_limit = 100 19 | 20 | def _dblsha256(data): 21 | return _sha256(_sha256(data).digest()).digest() 22 | 23 | def init_generation3(tmpl, script, override_cb=False): 24 | if (not tmpl.cbtxn is None) and not (override_cb and ('generate' in tmpl.mutations)): 25 | return (0, False) 26 | 27 | if len(script) >= 0xfd: 28 | return (0, True) 29 | 30 | sh = b'' 31 | h = tmpl.height 32 | while h > 127: 33 | sh += _pack('>= 8 35 | sh += _pack(' coinbase_size_limit: 45 | return (0, True) 46 | 47 | data = b'' 48 | data += b"\x01\0\0\0" # txn ver 49 | data += b"\x01" # input count 50 | data += b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" # prevout 51 | data += b"\xff\xff\xff\xff" # index (-1) 52 | data += _pack(' tmpl.sizelimit: 62 | return (0, True) 63 | 64 | txn = _Transaction(None) 65 | 66 | txn.data = data 67 | 68 | tmpl.cbtxn = txn 69 | 70 | tmpl.mutations.add('coinbase/append') 71 | tmpl.mutations.add('coinbase') 72 | tmpl.mutations.add('generate') 73 | 74 | return (tmpl.cbvalue, True) 75 | init_generation2 = init_generation3 76 | 77 | def init_generation(tmpl, script, override_cb=False): 78 | return init_generation2(tmpl, script, override_cb)[0] 79 | 80 | def _hash_transactions(tmpl): 81 | for txn in tmpl.txns: 82 | if hasattr(txn, 'hash_'): 83 | continue 84 | txn.hash_ = _dblsha256(txn.data) 85 | return True 86 | 87 | def _build_merkle_branches(tmpl): 88 | if hasattr(tmpl, '_mrklbranch'): 89 | return True 90 | 91 | if not _hash_transactions(tmpl): 92 | return False 93 | 94 | branchcount = len(tmpl.txns).bit_length() 95 | branches = [] 96 | 97 | merklehashes = [None] + [txn.hash_ for txn in tmpl.txns] 98 | while len(branches) < branchcount: 99 | branches.append(merklehashes[1]) 100 | if len(merklehashes) % 2: 101 | merklehashes.append(merklehashes[-1]) 102 | merklehashes = [None] + [_dblsha256(merklehashes[i] + merklehashes[i + 1]) for i in range(2, len(merklehashes), 2)] 103 | 104 | tmpl._mrklbranch = branches 105 | 106 | return True 107 | 108 | def _build_merkle_root(tmpl, coinbase): 109 | if not _build_merkle_branches(tmpl): 110 | return None 111 | 112 | lhs = _dblsha256(coinbase) 113 | 114 | for rhs in tmpl._mrklbranch: 115 | lhs = _dblsha256(lhs + rhs) 116 | 117 | return lhs 118 | 119 | _cbScriptSigLen = 4 + 1 + 36 120 | 121 | def _append_cb(tmpl, append, appended_at_offset = None): 122 | coinbase = tmpl.cbtxn.data 123 | # The following can be done better in both Python 2 and Python 3, but this way works with both 124 | origLen = ord(coinbase[_cbScriptSigLen:_cbScriptSigLen+1]) 125 | appendsz = len(append) 126 | 127 | if origLen > coinbase_size_limit - appendsz: 128 | return None 129 | 130 | if len(tmpl.cbtxn.data) + tmpl.txns_datasz + appendsz > tmpl.sizelimit: 131 | return None 132 | 133 | cbExtraNonce = _cbScriptSigLen + 1 + origLen 134 | if not appended_at_offset is None: 135 | appended_at_offset[0] = cbExtraNonce 136 | 137 | newLen = origLen + appendsz 138 | coinbase = coinbase[:_cbScriptSigLen] + chr(newLen).encode('ascii') + coinbase[_cbScriptSigLen+1:cbExtraNonce] + append + coinbase[cbExtraNonce:] 139 | 140 | return coinbase 141 | 142 | def append_coinbase_safe2(tmpl, append, extranoncesz = 0, merkle_only = False): 143 | if 'coinbase/append' not in tmpl.mutations and 'coinbase' not in tmpl.mutations: 144 | raise RuntimeError('Coinbase appending not allowed by template') 145 | 146 | datasz = len(tmpl.cbtxn.data) 147 | if extranoncesz == sizeof_workid: 148 | extranoncesz += 1 149 | elif not merkle_only: 150 | if extranoncesz < sizeof_workid: 151 | extranoncesz = sizeof_workid 152 | availsz = coinbase_size_limit - extranoncesz - ord(tmpl.cbtxn.data[_cbScriptSigLen:_cbScriptSigLen+1]) 153 | 154 | current_blocksize = len(tmpl.cbtxn.data) + tmpl.txns_datasz 155 | if current_blocksize > tmpl.sizelimit: 156 | return 0 157 | availsz2 = tmpl.sizelimit - current_blocksize 158 | if availsz2 < availsz: 159 | availsz = availsz2 160 | 161 | if len(append) > availsz: 162 | return availsz 163 | 164 | newcb = _append_cb(tmpl, append) 165 | if newcb is None: 166 | raise RuntimeError('Append failed') 167 | 168 | return availsz 169 | append_coinbase_safe = append_coinbase_safe2 170 | 171 | def _extranonce(tmpl, workid): 172 | coinbase = tmpl.cbtxn.data 173 | if not workid: 174 | return coinbase 175 | extradata = _pack(' tmpl.maxtime): 183 | timehdr = tmpl.maxtime 184 | return _pack(' maxtime_expire_limit: 192 | out_expire[0] = maxtime_expire_limit 193 | 194 | def _sample_data(tmpl, dataid): 195 | cbuf = _pack('= tmpl.expires: 259 | return 0 260 | return tmpl.expires - age 261 | 262 | def work_left(tmpl): 263 | if not tmpl.version: 264 | return 0 265 | if 'coinbase/append' not in tmpl.mutations and 'coinbase' not in tmpl.mutations: 266 | return 1 267 | return 0xffffffffffffffff - tmpl.next_dataid 268 | 269 | def _varintEncode(n): 270 | if n < 0xfd: 271 | return _pack('