def check_bip143_tx(self, tx_u_hex, tx_s_hex, txs_out_value_scripthex_pair, tx_in_count, tx_out_count, version, lock_time): tx_u = Tx.from_hex(tx_u_hex) tx_s = Tx.from_hex(tx_s_hex) txs_out = [ TxOut(int(coin_value * 1e8), h2b(script_hex)) for coin_value, script_hex in txs_out_value_scripthex_pair ] for tx in (tx_u, tx_s): self.assertEqual(len(tx.txs_in), tx_in_count) self.assertEqual(len(tx.txs_out), tx_out_count) self.assertEqual(tx.version, version) self.assertEqual(tx.lock_time, lock_time) tx.set_unspents(txs_out) self.check_unsigned(tx_u) self.check_signed(tx_s) tx_hex = tx_u.as_hex() self.assertEqual(tx_hex, tx_u_hex) tx_hex = tx_s.as_hex() self.assertEqual(tx_hex, tx_s_hex) tx_u_prime = self.unsigned_copy(tx_s) tx_hex = tx_u_prime.as_hex() self.assertEqual(tx_hex, tx_u_hex) self.assertEqual(b2h_rev(double_sha256(h2b(tx_s_hex))), tx_s.w_id()) self.assertEqual(b2h_rev(double_sha256(h2b(tx_u_hex))), tx_u.w_id()) self.assertEqual(b2h_rev(double_sha256(h2b(tx_u_hex))), tx_u.id()) return tx_u, tx_s
def _get_merkle_root(self, merkle_s, start_hash, pos): hash_decode = lambda x: x.decode('hex')[::-1] hash_encode = lambda x: x[::-1].encode('hex') h = hash_decode(start_hash) # i is the "level" or depth of the binary merkle tree. # item is the complementary hash on the merkle tree at this level for i, item in enumerate(merkle_s): # figure out if it's the left item or right item at this level if pos >> i & 1: # right item (odd at this level) h = double_sha256(hash_decode(item) + h) else: # left item (even at this level) h = double_sha256(h + hash_decode(item)) return hash_encode(h)
def recurse(level_widths, level_index, node_index, hashes, flags, flag_index, tx_acc): idx, r = divmod(flag_index, 8) mask = (1 << r) flag_index += 1 if flags[idx] & mask == 0: h = hashes.pop() return h, flag_index if level_index == len(level_widths) - 1: h = hashes.pop() tx_acc.append(h) return h, flag_index # traverse the left left_hash, flag_index = recurse( level_widths, level_index+1, node_index*2, hashes, flags, flag_index, tx_acc) # is there a right? if node_index*2+1 < level_widths[level_index+1]: right_hash, flag_index = recurse( level_widths, level_index+1, node_index*2+1, hashes, flags, flag_index, tx_acc) if left_hash == right_hash: raise ValueError("merkle hash has same left and right value at node %d" % node_index) else: right_hash = left_hash return double_sha256(left_hash + right_hash), flag_index
def get_merkle_root(self, merkle_s, start_hash, pos): """Given a merkle hash list merkle_s and the starting point start_hash Hash all the way to the root and get the merkle_root. """ h = hash_decode(start_hash) # i is the "level" or depth of the binary merkle tree. # item is the complementary hash on the merkle tree at this level for i, item in enumerate(merkle_s): # figure out if it's the left item or right item at this level if pos >> i & 1: # right item (odd at this level) h = double_sha256(hash_decode(item) + h) else: # left item (even at this level) h = double_sha256(h + hash_decode(item)) return hash_encode(h)
def _parse_next_message(self): # read magic header reader = self.reader blob = yield from reader.readexactly(len(self.magic_header)) if blob != self.magic_header: raise BitcoinProtocolError("bad magic: got %s" % binascii.hexlify(blob)) # read message name message_size_hash_bytes = yield from reader.readexactly(20) message_name_bytes = message_size_hash_bytes[:12] message_name = message_name_bytes.replace(b"\0", b"").decode("utf8") # get size of message size_bytes = message_size_hash_bytes[12:16] size = int.from_bytes(size_bytes, byteorder="little") if size > self.MAX_MESSAGE_SIZE: raise BitcoinProtocolError("absurdly large message size %d" % size) # read the hash, then the message transmitted_hash = message_size_hash_bytes[16:20] message_data = yield from reader.readexactly(size) # check the hash actual_hash = encoding.double_sha256(message_data)[:4] if actual_hash != transmitted_hash: raise BitcoinProtocolError("checksum is WRONG: %s instead of %s" % ( binascii.hexlify(actual_hash), binascii.hexlify(transmitted_hash))) logging.debug("message %s: %s (%d byte payload)", self, message_name, len(message_data)) # parse the blob into a BitcoinProtocolMessage object data = parse_from_data(message_name, message_data) return message_name, data
def test_txhash(self): """ Performs a test of the reference wallet's tx hashing against a known blockchain tx. """ # 's' and 'expected' are from: # https://blockchain.info/rawtx/a8196acaf3938b988f9816ae3e9da1df5a04afff0b5b460e4c1dc4a08dd52109?format=hex s = ("0100000002bca066b9cfe1eb81e667f219a442acdc5c5e2e470610659a314" "74dfb5e29c552000000008c493046022100b2857170045d5e59112e0d5200" "4a8f65d18945e52d42c2eb12f7d3c2314600b802210098bdab40dfe38b5d4" "fe02e1fa3057ada3e0a982a5c7979eabff86395e2a911e8014104a8075344" "0c651f7191f46085411679545486f1dc6bf34cdaba453966c5fe7cc34f3dd" "c15ae321974f426807faa34b3fc10034e129222067ec053c409a6ac1f30ff" "ffffffe047c65f9e560d799580f6a965c12a059ca1e82cebc3e220659ba29" "ee31c8d0a010000008b48304502206b7eaa2dec17b53022b57a55b48ac245" "6f1c22d87b0170aa969de04146b80bbc022100d6700f6eb9bde89c35b0545" "588c06dcfed95e0502941d79786b5ea24eafc2cfe01410435d1d08c6f5296" "0d056e60c3b5c858e5299c1a688395b589dbde6b58861b20fdd7ee58832b3" "528845973765038cafc1c81280dc635ee202ce06aa4a373db012fffffffff" "0200f2052a010000001976a914315bfd9ee07d6779e44b8e07229650f039f" "0942788aced931600000000001976a91484004861f9a742fc83ad4ab83c42" "e709b512df1888ac00000000") expected = "a8196acaf3938b988f9816ae3e9da1df5a04afff0b5b460e4c1dc4a08dd52109" expected = bytes.fromhex(expected) actual = encoding.double_sha256(bytes.fromhex(s)) # Reverse the bytes to flow lsb -> msb actual = actual[::-1] assert actual == expected, "tx hash calculation mismatch\n" + "Expected: " + self.hexstr(expected) + "\nActual: " + self.hexstr(actual)
def send_msg(self, message_name, **kwargs): message_data = pack_from_data(message_name, **kwargs) message_type = message_name.encode("utf8") message_type_padded = (message_type+(b'\0'*12))[:12] message_size = struct.pack("<L", len(message_data)) message_checksum = encoding.double_sha256(message_data)[:4] packet = b"".join([ self.magic_header, message_type_padded, message_size, message_checksum, message_data ]) logging.debug("sending message %s [%d bytes] to %s", message_type.decode("utf8"), len(packet), self) self.bytes_writ += len(packet) self.transport.write(packet)
def subkey(self, path): """ path: of the form "K" where K is an integer index, or "K/N" where N is usually a 0 (deposit address) or 1 (change address) """ t = path.split("/") if len(t) == 2: n, for_change = t else: n, = t for_change = 0 b = (str(n) + ':' + str(for_change) + ':').encode("utf8") + self.master_public_key() offset = from_bytes_32(double_sha256(b)) if self.master_private_key(): return Key( secret_exponent=((self.master_private_key() + offset) % ORDER), prefer_uncompressed=True ) p1 = offset * ecdsa.generator_secp256k1 x, y = self.public_pair() p2 = ecdsa.Point(ecdsa.generator_secp256k1.curve(), x, y, ORDER) p = p1 + p2 return Key(public_pair=p.pair(), prefer_uncompressed=True)
def signature_for_hash_type_segwit(self, script, tx_in_idx, hash_type): return from_bytes_32( double_sha256( self.segwit_signature_preimage(script, tx_in_idx, hash_type)))
def w_hash(self): return double_sha256(self.as_bin())
def _bitcoin_message_hash(data): prefix = b"\x18Bitcoin Signed Message:\n" varint = _encode_varint(len(data)) return double_sha256(prefix + varint + data)
def do_test(blob, expected_hash): self.assertEqual(encoding.double_sha256(blob), expected_hash)
for i3 in '6': # sure for i4 in '123456789': # NOT sure for i6 in '123456789': # NOT sure for i7 in '123456789': # NOT sure # make candidate private key key = k1 + i1 + k2 + i2 + k3 + i3 + k4 + i4 + k5 + i3 + k6 + i6 + k7 + i7 + k8 print('Trying {}...\t'.format(key), end='') # the private key encoded in base58 contains also a checksum at the end to check validity # when a candidate key is made by concatenation as above it will most likely not be valid # so we correct the checksum and compression byte of the candidate key data = a2b_base58(key) data, the_hash = data[:-4], data[-4:] data = data[:-1] + b'\01' fixed_key = b2a_base58(data + double_sha256(data)[:4]) # calculate the P2SH SegWit address for this private key k = Key.from_text(fixed_key) p2sh = p2sh_address(k) print('{}\t'.format(p2sh), end='') # compare with the published public key if p2sh[0:7] == '37CSnmm': print('Bingo!') exit(0) else: i = 0 stars = '' while p2sh[i] == '37CSnmm'[i]: stars += '*'
def test_sign(self, keynums_satoshi, out_addr, out_satoshi, change_keynum, change_satoshi, prevtx_keynums, prevtx_outputs, prevtx_inputs): """ Performs a tx signing test, comparing Polly's signed tx against the reference wallet. Basic tx signing parameters: keynums_satoshi - list of tuples (keynum, satoshis) with key indices and their unspent value to use as tx inputs. Funding above out_satoshi + change_satoshi will be fees. out_addr - output address in bitcoin address format. out_satoshi - output amount in satoshis. change_keynum - change key index in the wallet, use None for no change. change_satoshi - change amount in satoshis, use 0 for no change. Supporting (previous) txs will be created to fund keynums and are controlled by these parameters: prevtx_keynums - keynums will show up as outputs of previous txs. A number randomly picked from this list controls how many keynums are chosen to include per prev tx. prevtx_outputs - in addition to previous tx outputs funding keynums, other outputs may be present. A number randomly picked from this list controls how many ignored outputs are injected per keynum. prevtx_inputs - previous txs need inputs too. A number randomly picked from this list controls how many inputs are chosen per previous tx. """ total_in_satoshi = sum(satoshi for _, satoshi in keynums_satoshi) fee_satoshi = total_in_satoshi - out_satoshi - change_satoshi chain0 = self.wallet.subkey(0, is_hardened=True).subkey(0) chain1 = self.wallet.subkey(0, is_hardened=True).subkey(1) assert total_in_satoshi >= out_satoshi + change_satoshi assert len(keynums_satoshi) <= 32 # # Step 1: send the inputs and outputs to use in the signed tx # # Create the (key num, compressed public key) tuple, input keys assume an m/0h/0/keynum path for now. keys = [ (keynum, encoding.public_pair_to_sec(chain0.subkey(keynum).public_pair)) for (keynum, _) in keynums_satoshi ] # Convert base58 address to raw hex address out_addr_160 = encoding.bitcoin_address_to_hash160_sec(out_addr) print() print("Sign tx parameters:", "") for i, (keynum, satoshi) in enumerate(keynums_satoshi): print("{:<10}{:16.8f} btc < key {}".format( " inputs" if 0 == i else "", satoshi / 100000000, keynum)) print("{:<10}{:16.8f} btc > {}".format(" output", out_satoshi / 100000000, self.hexstr(out_addr_160))) print("{:<10}{:16.8f} btc > key {}".format(" change", change_satoshi / 100000000, change_keynum)) print("{:<10}{:16.8f} btc".format(" fee", fee_satoshi / 100000000)) print("{:<10}{:16.8f} btc".format(" total", total_in_satoshi / 100000000)) print() print(self.PAD.format("Send tx parameters"), end='') # ---> send to Polly self.polly.send_sign_tx(keys, out_addr_160, out_satoshi, change_keynum, change_satoshi) print(self.__outok()) # # Step 2: send previous txs to fund the inputs # print() cur = 0 prevtx_info = [] while cur < len(keynums_satoshi): prevtx_outputs_satoshi = [] # Calculate how many keynums will be associated with this prev tx end = min(cur + random.choice(prevtx_keynums), len(keynums_satoshi)) # Create the prev tx output list for keynum, satoshi in keynums_satoshi[cur:end]: # Inject a random number of outputs not associated with tx input keynums for _ in range(0, random.choice(prevtx_outputs)): prevtx_outputs_satoshi.append( (random.randint(0, 0x7FFFFFFF), random.randint(0, 2099999997690000))) # Add the outputs funding the tx input keynums prevtx_outputs_satoshi.append((keynum, satoshi)) # Create output script addr = chain0.subkey(keynum, as_private=True).bitcoin_address() script = standard_tx_out_script(addr) # Capture some info we'll use later to verify the signed tx prevtx_info.append(( keynum, satoshi, script, 0, # This is the hash and will be replaced later len(prevtx_outputs_satoshi) - 1)) # Index of the valid output print("{:30}{}".format( "Make prev tx for keys", " ".join( str(keynum) for (keynum, _, _, _, _) in prevtx_info[cur:]))) # Create the prev tx prevtx = self.create_prev_tx( win=Wallet.from_master_secret( bytes(0)), # create a dummy wallet in_keynum=list(range(0, random.choice(prevtx_inputs))), sources_per_input=1, wout=chain0, out_keynum_satoshi=prevtx_outputs_satoshi, fees_satoshi=random.randint(100, 1000)) # We have built the prev tx, calculate its hash (and reverse the bytes) prevtx_hash = encoding.double_sha256(prevtx)[::-1] # Update the hashes now that we have a full prev tx for i, (keynum, satoshi, script, _, outidx) in enumerate(prevtx_info[cur:]): prevtx_info[i + cur] = (keynum, satoshi, script, prevtx_hash, outidx) # Create the index table that matches a keynum index with an ouput index in this prev tx idx_table = [ (keynum_idx + cur, outidx) for keynum_idx, (_, _, _, _, outidx) in enumerate(prevtx_info[cur:]) ] print(self.PAD.format("Send prev tx "), end='') # ---> send to Polly self.polly.send_prev_tx(idx_table, prevtx) print(self.__outok()) cur = end # # Step 3: generate a signed tx with the reference wallet and compare against Polly's # spendables = [] wifs = [] # Make sure that the inputs add up correctly, and prep the input_sources for reference wallet signing for (keynum, satoshi, script, prevtx_hash, outidx) in prevtx_info: spendables.append(Spendable(satoshi, script, prevtx_hash, outidx)) wifs.append(chain0.subkey(keynum, as_private=True).wif()) change_addr = chain1.subkey(change_keynum).bitcoin_address() payables = [(out_addr, out_satoshi), (change_addr, change_satoshi)] print() print(self.PAD.format("Make reference signature")) signed_tx = create_signed_tx(spendables, payables, wifs, fee_satoshi) signed_tx = self.get_tx_bytes(signed_tx) print(self.PAD.format("Get signed tx"), end='', flush=True) # <--- get the signed tx from Polly polly_signed_tx = self.polly.send_get_signed_tx() #print(self.txstr(polly_signed_tx)) #print(self.txstr(signed_tx)) print(self.__outok()) # Compare reference wallet signed tx with polly's assert signed_tx == polly_signed_tx, "test_sign: signature mismatch\nExpected:\n" + self.hexstr( signed_tx) + "\n\nActual:\n" + self.hexstr(polly_signed_tx)
def test_sign(self, keynums_satoshi, out_addr, out_satoshi, change_keynum, change_satoshi, prevtx_keynums, prevtx_outputs, prevtx_inputs): """ Performs a tx signing test, comparing Polly's signed tx against the reference wallet. Basic tx signing parameters: keynums_satoshi - list of tuples (keynum, satoshis) with key indices and their unspent value to use as tx inputs. Funding above out_satoshi + change_satoshi will be fees. out_addr - output address in bitcoin address format. out_satoshi - output amount in satoshis. change_keynum - change key index in the wallet, use None for no change. change_satoshi - change amount in satoshis, use 0 for no change. Supporting (previous) txs will be created to fund keynums and are controlled by these parameters: prevtx_keynums - keynums will show up as outputs of previous txs. A number randomly picked from this list controls how many keynums are chosen to include per prev tx. prevtx_outputs - in addition to previous tx outputs funding keynums, other outputs may be present. A number randomly picked from this list controls how many ignored outputs are injected per keynum. prevtx_inputs - previous txs need inputs too. A number randomly picked from this list controls how many inputs are chosen per previous tx. """ total_in_satoshi = sum(satoshi for _, satoshi in keynums_satoshi) fee_satoshi = total_in_satoshi - out_satoshi - change_satoshi chain0 = self.wallet.subkey(0, is_hardened = True).subkey(0) chain1 = self.wallet.subkey(0, is_hardened = True).subkey(1) assert total_in_satoshi >= out_satoshi + change_satoshi assert len(keynums_satoshi) <= 32 # # Step 1: send the inputs and outputs to use in the signed tx # # Create the (key num, compressed public key) tuple, input keys assume an m/0h/0/keynum path for now. keys = [(keynum, encoding.public_pair_to_sec(chain0.subkey(keynum).public_pair)) for (keynum, _) in keynums_satoshi] # Convert base58 address to raw hex address out_addr_160 = encoding.bitcoin_address_to_hash160_sec(out_addr) print() print("Sign tx parameters:", "") for i, (keynum, satoshi) in enumerate(keynums_satoshi): print("{:<10}{:16.8f} btc < key {}".format (" inputs" if 0 == i else "", satoshi / 100000000, keynum)) print("{:<10}{:16.8f} btc > {}".format (" output", out_satoshi / 100000000, self.hexstr(out_addr_160))) print("{:<10}{:16.8f} btc > key {}".format (" change", change_satoshi / 100000000, change_keynum)) print("{:<10}{:16.8f} btc".format (" fee", fee_satoshi / 100000000)) print("{:<10}{:16.8f} btc".format (" total", total_in_satoshi / 100000000)) print() print(self.PAD.format("Send tx parameters"), end='') # ---> send to Polly self.polly.send_sign_tx(keys, out_addr_160, out_satoshi, change_keynum, change_satoshi) print(self.__outok()) # # Step 2: send previous txs to fund the inputs # print() cur = 0 prevtx_info = [] while cur < len(keynums_satoshi) : prevtx_outputs_satoshi = [] # Calculate how many keynums will be associated with this prev tx end = min(cur + random.choice(prevtx_keynums), len(keynums_satoshi)) # Create the prev tx output list for keynum, satoshi in keynums_satoshi[cur:end] : # Inject a random number of outputs not associated with tx input keynums for _ in range(0, random.choice(prevtx_outputs)) : prevtx_outputs_satoshi.append((random.randint(0, 0x7FFFFFFF), random.randint(0, 2099999997690000))) # Add the outputs funding the tx input keynums prevtx_outputs_satoshi.append((keynum, satoshi)) # Create output script addr = chain0.subkey(keynum, as_private = True).bitcoin_address() script = standard_tx_out_script(addr) # Capture some info we'll use later to verify the signed tx prevtx_info.append((keynum, satoshi, script, 0, # This is the hash and will be replaced later len(prevtx_outputs_satoshi) - 1)) # Index of the valid output print("{:30}{}".format("Make prev tx for keys", " ".join(str(keynum) for (keynum, _, _, _, _) in prevtx_info[cur:]))) # Create the prev tx prevtx = self.create_prev_tx(win = Wallet.from_master_secret(bytes(0)), # create a dummy wallet in_keynum = list(range(0, random.choice(prevtx_inputs))), sources_per_input = 1, wout = chain0, out_keynum_satoshi = prevtx_outputs_satoshi, fees_satoshi = random.randint(100, 1000)) # We have built the prev tx, calculate its hash (and reverse the bytes) prevtx_hash = encoding.double_sha256(prevtx)[::-1] # Update the hashes now that we have a full prev tx for i, (keynum, satoshi, script, _, outidx) in enumerate(prevtx_info[cur:]) : prevtx_info[i + cur] = (keynum, satoshi, script, prevtx_hash, outidx) # Create the index table that matches a keynum index with an ouput index in this prev tx idx_table = [(keynum_idx + cur, outidx) for keynum_idx, (_, _, _, _, outidx) in enumerate(prevtx_info[cur:])] print(self.PAD.format("Send prev tx "), end='') # ---> send to Polly self.polly.send_prev_tx(idx_table, prevtx) print(self.__outok()) cur = end # # Step 3: generate a signed tx with the reference wallet and compare against Polly's # spendables = [] wifs = [] # Make sure that the inputs add up correctly, and prep the input_sources for reference wallet signing for (keynum, satoshi, script, prevtx_hash, outidx) in prevtx_info: spendables.append(Spendable(satoshi, script, prevtx_hash, outidx)) wifs.append(chain0.subkey(keynum, as_private = True).wif()) change_addr = chain1.subkey(change_keynum).bitcoin_address() payables = [(out_addr, out_satoshi), (change_addr, change_satoshi)] print() print(self.PAD.format("Make reference signature")) signed_tx = create_signed_tx(spendables, payables, wifs, fee_satoshi) signed_tx = self.get_tx_bytes(signed_tx) print(self.PAD.format("Get signed tx"), end='', flush = True) # <--- get the signed tx from Polly polly_signed_tx = self.polly.send_get_signed_tx() #print(self.txstr(polly_signed_tx)) #print(self.txstr(signed_tx)) print(self.__outok()) # Compare reference wallet signed tx with polly's assert signed_tx == polly_signed_tx, "test_sign: signature mismatch\nExpected:\n" + self.hexstr(signed_tx) + "\n\nActual:\n" + self.hexstr(polly_signed_tx)
def _calculate_hash(self): s = io.BytesIO() self.stream_header(s) return double_sha256(s.getvalue())
def digest(self): """Returns the digest of the data.""" return double_sha256(self.data)