def multisig_to_redeemscript(public_keys, m): if m > 16: raise ValueError( 'More than the allowed maximum of 16 public keys cannot be used.') redeemscript = int_to_unknown_bytes(m + 80) for key in public_keys: key_byte = hex_to_bytes(key) length = len(key_byte) if length not in (33, 65): raise ValueError( 'At least one of the provided public keys is of invalid length {}.' .format(length)) redeemscript += script_push(length) + key_byte redeemscript += int_to_unknown_bytes( len(public_keys) + 80 ) + b'\xae' # Only works for n = len(public_keys) < 17. OK due to P2SH script-length limitation. if len(redeemscript) > 520: raise ValueError( 'The redeemScript exceeds the allowed 520-byte limitation with the number of public keys.' ) return redeemscript
def multisig_to_redeemscript(public_keys, m): if m > len(public_keys): raise ValueError( 'Required signatures cannot be more than the total number of public keys.' ) redeemscript = int_to_unknown_bytes(m + 80) for key in public_keys: length = len(key) if length not in (33, 65): raise ValueError( 'At least one of the provided public keys is of invalid length {}.' .format(length)) redeemscript += script_push(length) + key redeemscript += ( int_to_unknown_bytes(len(public_keys) + 80) + b'\xae' ) # Only works for n = len(public_keys) < 17. OK due to P2SH script-length limitation. if len(redeemscript) > 520: raise ValueError( 'The redeemScript exceeds the allowed 520-byte limitation with the number of public keys.' ) return redeemscript
def estimate_tx_fee(n_in, n_out, satoshis, compressed): if not satoshis: return 0 estimated_size = (n_in * (148 if compressed else 180) + len(int_to_unknown_bytes(n_in, byteorder='little')) + n_out * 34 + len(int_to_unknown_bytes(n_out, byteorder='little')) + 8) return estimated_size * satoshis
def estimate_tx_fee(in_size, n_in, out_size, n_out, satoshis): if not satoshis: return 0 estimated_size = (in_size + len(int_to_unknown_bytes(n_in, byteorder='little')) + out_size + len(int_to_unknown_bytes(n_out, byteorder='little')) + 8) estimated_fee = estimated_size * satoshis logging.debug('Estimated fee: {} satoshis for {} bytes'.format( estimated_fee, estimated_size)) return estimated_fee
def b58decode(string): alphabet = BASE58_ALPHABET num = 0 for char in string: num *= 58 try: index = alphabet.index(char) except ValueError: raise ValueError('"{}" is an invalid base58 encoded ' 'character.'.format(char)) from None num += index bytestr = int_to_unknown_bytes(num) pad = 0 for char in string: if char == '1': pad += 1 else: break return b'\x00' * pad + bytestr
def construct_output_block(outputs): output_block = b'' for data in outputs: dest, amount = data # Real recipient if amount: script = (OP_DUP + OP_HASH160 + OP_PUSH_20 + address_to_public_key_hash(dest) + OP_EQUALVERIFY + OP_CHECKSIG) output_block += amount.to_bytes(8, byteorder='little') # Blockchain storage else: script = (OP_RETURN + len(dest).to_bytes(1, byteorder='little') + dest) output_block += b'\x00\x00\x00\x00\x00\x00\x00\x00' output_block += int_to_unknown_bytes(len(script), byteorder='little') output_block += script return output_block
def estimate_tx_fee(n_in, n_out, satoshis, compressed): if not satoshis: return 0 estimated_size = (n_in * (148 if compressed else 180) + len(int_to_unknown_bytes(n_in, byteorder='little')) + n_out * 34 + len(int_to_unknown_bytes(n_out, byteorder='little')) + 8) estimated_fee = estimated_size * satoshis logging.debug('Estimated fee: {} satoshis for {} bytes'.format( estimated_fee, estimated_size)) return estimated_fee
def estimate_tx_fee(in_size, n_in, out_size, n_out, satoshis, segwit=False): if not satoshis: return 0 estimated_size = math.ceil( in_size + len(int_to_unknown_bytes(n_in, byteorder='little')) + out_size + len(int_to_unknown_bytes(n_out, byteorder='little')) + 8 # Accounting for magic header vBytes ('0001') + (0.5 if segwit else 0)) estimated_fee = estimated_size * satoshis logging.debug('Estimated fee: {} satoshis for {} bytes'.format( estimated_fee, estimated_size)) return estimated_fee
def create_p2pkh_transaction(private_key, unspents, outputs): public_key = private_key.public_key public_key_len = len(public_key).to_bytes(1, byteorder='little') version = VERSION_1 lock_time = LOCK_TIME sequence = SEQUENCE hash_type = HASH_TYPE input_count = int_to_unknown_bytes(len(unspents), byteorder='little') output_count = int_to_unknown_bytes(len(outputs), byteorder='little') output_block = construct_output_block(outputs) # Optimize for speed, not memory, by pre-computing values. inputs = [] for unspent in unspents: script = hex_to_bytes(unspent.script) script_len = int_to_unknown_bytes(len(script), byteorder='little') txid = hex_to_bytes(unspent.txid)[::-1] txindex = unspent.txindex.to_bytes(4, byteorder='little') inputs.append(TxIn(script, script_len, txid, txindex)) for i, txin in enumerate(inputs): hashed = sha256(version + input_count + b''.join(ti.txid + ti.txindex + OP_0 + sequence for ti in islice(inputs, i)) + txin.txid + txin.txindex + txin.script_len + txin.script + sequence + b''.join(ti.txid + ti.txindex + OP_0 + sequence for ti in islice(inputs, i + 1, None)) + output_count + output_block + lock_time + hash_type) signature = private_key.sign(hashed) + b'\x01' script_sig = (len(signature).to_bytes(1, byteorder='little') + signature + public_key_len + public_key) txin.script = script_sig txin.script_len = int_to_unknown_bytes(len(script_sig), byteorder='little') return bytes_to_hex(version + input_count + construct_input_block(inputs) + output_count + output_block + lock_time)
def make_compliant_sig(signature): """Adhere to BIP-62: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki """ r, s = decode_dss_signature(signature) s = GROUP_ORDER - s if s > GROUP_ORDER // 2 else s r = int_to_unknown_bytes(r) s = int_to_unknown_bytes(s) if r[0] & 0x80: r = b'\x00' + r if s[0] & 0x80: s = b'\x00' + s r = b'\x02' + int_to_unknown_bytes(len(r)) + r s = b'\x02' + int_to_unknown_bytes(len(s)) + s return b'\x30' + int_to_unknown_bytes(len(r) + len(s)) + r + s
def b58decode(string): alphabet_index = BASE58_ALPHABET_INDEX num = 0 try: for char in string: num *= 58 num += alphabet_index[char] except KeyError: raise ValueError('"{}" is an invalid base58 encoded character.'.format( char)) from None bytestr = int_to_unknown_bytes(num) pad = 0 for char in string: if char == '1': pad += 1 else: break return b'\x00' * pad + bytestr
def test_zero(self): assert int_to_unknown_bytes(0) == b'\x00'
def test_little(self): assert int_to_unknown_bytes(BIG_INT, 'little') == BYTES_LITTLE
def test_default(self): assert int_to_unknown_bytes(BIG_INT) == BYTES_BIG
def create_sweep_transaction(private_keys, destination_address, amount=None, currency='satoshi', fee=None, leftover=None, message=None, compressed=True): private_key_map = {} unspents = [] for key in private_keys: utxos = key.get_unspents() unspents += utxos for utx in utxos: private_key_map[utx.txid] = key version = VERSION_1 lock_time = LOCK_TIME sequence = SEQUENCE hash_type = HASH_TYPE input_count = int_to_unknown_bytes(len(unspents), byteorder='little') # Construct the outputs, taking the fee into account sum_of_unspents = sum([int(x.amount) for x in unspents]) amount = amount or sum_of_unspents outputs = [(destination_address, sum_of_unspents, currency)] unspents, outputs = sanitize_tx_data(unspents, outputs, fee or get_fee_cached(), leftover or private_keys[0].address, combine=True, message=message, compressed=compressed, sweep=True) output_count = int_to_unknown_bytes(len(outputs), byteorder='little') output_block = construct_output_block(outputs) # Optimize for speed, not memory, by pre-computing values. inputs = [] for unspent in unspents: script = hex_to_bytes(unspent.script) script_len = int_to_unknown_bytes(len(script), byteorder='little') txid = hex_to_bytes(unspent.txid)[::-1] txindex = unspent.txindex.to_bytes(4, byteorder='little') inputs.append(TxIn(script, script_len, txid, txindex)) for i, txin in enumerate(inputs): hashed = sha256(version + input_count + b''.join(ti.txid + ti.txindex + OP_0 + sequence for ti in islice(inputs, i)) + txin.txid + txin.txindex + txin.script_len + txin.script + sequence + b''.join(ti.txid + ti.txindex + OP_0 + sequence for ti in islice(inputs, i + 1, None)) + output_count + output_block + lock_time + hash_type) private_key = private_key_map[unspents[i].txid] signature = private_key.sign(hashed) + b'\x01' public_key = private_key.public_key public_key_len = len(public_key).to_bytes(1, byteorder='little') script_sig = (len(signature).to_bytes(1, byteorder='little') + signature + public_key_len + public_key) txin.script = script_sig txin.script_len = int_to_unknown_bytes(len(script_sig), byteorder='little') return bytes_to_hex(version + input_count + construct_input_block(inputs) + output_count + output_block + lock_time)
def sanitize_tx_data(unspents, outputs, fee, leftover, combine=True, message=None, compressed=True): """ sanitize_tx_data() fee is in satoshis per byte. """ outputs = outputs.copy() for i, output in enumerate(outputs): dest, amount, currency = output outputs[i] = (dest, currency_to_satoshi_cached(amount, currency)) if not unspents: raise ValueError('Transactions must have at least one unspent.') messages = [] if message: if type(message) == int: messages.append((int_to_unknown_bytes(message), 0)) else: messages.append((hex_to_bytes(message), 0)) # Include return address in output count. num_outputs = len(outputs) + len(messages) + 1 sum_outputs = sum(out[1] for out in outputs) total_in = 0 if combine: # calculated_fee is in total satoshis. calculated_fee = estimate_tx_fee(len(unspents), num_outputs, fee, compressed) total_out = sum_outputs + calculated_fee unspents = unspents.copy() total_in += sum(unspent.amount for unspent in unspents) else: unspents = sorted(unspents, key=lambda x: x.amount) index = 0 for index, unspent in enumerate(unspents): total_in += unspent.amount calculated_fee = estimate_tx_fee(len(unspents[:index + 1]), num_outputs, fee, compressed) total_out = sum_outputs + calculated_fee if total_in >= total_out: break unspents[:] = unspents[:index + 1] remaining = total_in - total_out if remaining > 0: outputs.append((leftover, remaining)) elif remaining < 0: raise InsufficientFunds('Balance {} is less than {} (including ' 'fee).'.format(total_in, total_out)) outputs.extend(messages) return unspents, outputs