def confirm_fee(self, msg, out_change): coin = coindef.types[msg.coin_name] print "CHANGE OUT:", out_change # Calculate tx hashes for all provided input transactions txes = {} for tx in msg.transactions: hsh = binascii.hexlify(StreamTransactionHash.calculate(tx)[::-1]) txes[hsh] = tx # Check tx fees to_spend = 0 for inp in msg.inputs: try: tx = txes[binascii.hexlify(inp.prev_hash)] except: return proto.Failure(code=proto_types.Failure_Other, message="Prev hash %s not found in [%s]" % (binascii.hexlify(inp.prev_hash), ','.join(txes.keys()))) to_spend += tx.bin_outputs[inp.prev_index].amount spending = 0 for out in msg.outputs: spending += out.amount if out_change != None: change_amount = msg.outputs[out_change].amount else: change_amount = 0 est_size = estimate_size_kb(len(msg.inputs), len(msg.outputs)) maxfee = coin.maxfee_kb * est_size fee = to_spend - spending print "To spend:", to_spend print "Spending:", spending print "Est tx size:", est_size print "Maxfee:", maxfee print "Tx fee:", fee print "Change output amount:", change_amount print "Now please be patient..." if spending > to_spend: return proto.Failure(code=proto_types.Failure_NotEnoughFunds, message="Not enough funds") if fee > maxfee: # FIXME soft limit #return proto.Failure(code=proto_types.Failure_Other, message="Fee is over threshold") self.layout.show_high_fee(fee, coin) return self.yesno.request(proto_types.ButtonRequest_FeeOverThreshold, self.do_confirm_sign, *[msg, to_spend]) return self.do_confirm_sign(msg, to_spend)
def workflow(self, msg): if msg.inputs_count < 1: raise Exception(proto.Failure(message='Transaction must have at least one input')) if msg.outputs_count < 1: raise Exception(proto.Failure(message='Transaction must have at least one output')) bip32 = BIP32(self.iface.storage.get_node()) coin = coindef.types[msg.coin_name] version = 1 lock_time = 0 serialized_tx = '' signature = None checkhash = None to_spend = 0 spending = 0 change_amount = 0 outtx = StreamTransactionSerialize(msg.inputs_count, msg.outputs_count, version, lock_time) # foreach I: for i in range(msg.inputs_count): # Request I req = proto.TxRequest(request_type=proto_types.TXINPUT, details=proto_types.TxRequestDetailsType(request_index=i)) # Fill values from previous round if i > 0: req.serialized.serialized_tx = serialized_tx req.serialized.signature = signature req.serialized.signature_index = i - 1 serialized_tx = '' ret = yield req inp = ret.tx.inputs[0] # ----------- Calculate amount of I: amount = None # Request prevhash I, META ret = yield(proto.TxRequest(request_type=proto_types.TXMETA, details=proto_types.TxRequestDetailsType( tx_hash=inp.prev_hash))) amount_hash = StreamTransactionHash(ret.tx.inputs_cnt, ret.tx.outputs_cnt, version, lock_time) # foreach prevhash I: for i2 in range(ret.tx.inputs_cnt): # Request prevhash I ret2 = yield(proto.TxRequest(request_type=proto_types.TXINPUT, details=proto_types.TxRequestDetailsType( request_index=i2, tx_hash=inp.prev_hash))) amount_hash.serialize_input(ret2.tx.inputs[0]) # foreach prevhash O: for o2 in range(ret.tx.outputs_cnt): # Request prevhash O ret2 = yield(proto.TxRequest(request_type=proto_types.TXOUTPUT, details=proto_types.TxRequestDetailsType( request_index=o2, tx_hash=inp.prev_hash))) amount_hash.serialize_output(ret2.tx.bin_outputs[0]) if inp.prev_index == o2: # Store amount of I amount = ret2.tx.bin_outputs[0].amount to_spend += amount # Calculate hash of streamed tx, compare to prevhash I if inp.prev_hash != amount_hash.calc_txid()[::-1]: raise Exception(proto.Failure(message="Provided input data doesn't match to prev_hash")) # ------------- End of streaming amounts # Add META to StreamTransactionSign sign = StreamTransactionSign(i, msg.inputs_count, msg.outputs_count, version, lock_time) # Calculate hash for each input, then compare to checkhash check = StreamTransactionHash(msg.inputs_count, msg.outputs_count, version, lock_time, True) # foreach I: for i2 in range(msg.inputs_count): # Request I ret2 = yield(proto.TxRequest(request_type=proto_types.TXINPUT, details=proto_types.TxRequestDetailsType(request_index=i2))) check.serialize_input(ret2.tx.inputs[0]) # If I == I-to-be-signed: if i2 == i: # Fill scriptsig address = bip32.get_address(coin, list(ret2.tx.inputs[0].address_n)) private_key = bip32.get_private_node(list(ret2.tx.inputs[0].address_n)).private_key print "ADDRESS", address print "PRIVKEY", binascii.hexlify(private_key) secexp = string_to_number(private_key) sign.serialize_input(ret2.tx.inputs[0], address, secexp) else: # Add I to StreamTransactionSign sign.serialize_input(ret2.tx.inputs[0]) # foreach O: out_change = None for o2 in range(msg.outputs_count): # Request O ret2 = yield(proto.TxRequest(request_type=proto_types.TXOUTPUT, details=proto_types.TxRequestDetailsType(request_index=o2))) out = ret2.tx.outputs[0] if len(list(out.address_n)) and out.HasField('address'): raise Exception(proto.Failure(code=proto_types.Failure_Other, message="Cannot have both address and address_n for the output")) # Calculate proper address for given address_n if len(list(out.address_n)): if out_change == None: out.address = bip32.get_address(coin, list(out.address_n)) out.ClearField('address_n') out_change = o2 # Remember which output is supposed to be a change else: raise Exception(proto.Failure(code=proto_types.Failure_Other, message="Only one change output allowed")) if i == 0: change_amount = out.amount # If I=0: if i == 0: spending += out.amount print "SENDING", out.amount, "TO", out.address if out_change != o2: # confirm non change output self.iface.layout.show_output(coin, out.address, out.amount) ret = yield proto.ButtonRequest(code=proto_types.ButtonRequest_ConfirmOutput) if not isinstance(ret, proto.ButtonAck): raise Exception(proto.Failure(code=proto_types.Failure_Other, message="Signing aborted")) check.serialize_output(compile_TxOutput(out)) # Add O to StreamTransactionSign sign.serialize_output(compile_TxOutput(out)) if i == 0: checkhash = check.calc_txid() else: if check.calc_txid() != checkhash: raise Exception(proto.Failure(message='Serialization check failed')) # Sign StreamTransactionSign (signature, pubkey) = sign.sign() serialized_tx += outtx.serialize_input(inp, signature, pubkey) print "SIGNATURE", binascii.hexlify(signature) print "PUBKEY", binascii.hexlify(pubkey) if spending > to_spend: raise Exception(proto.Failure(code=proto_types.Failure_NotEnoughFunds, message="Not enough funds")) est_size = estimate_size_kb(msg.inputs_count, msg.outputs_count) maxfee = coin.maxfee_kb * est_size fee = to_spend - spending if fee > maxfee: self.iface.layout.show_high_fee(fee, coin) ret = yield proto.ButtonRequest(code=proto_types.ButtonRequest_FeeOverThreshold) if not isinstance(ret, proto.ButtonAck): raise Exception(proto.Failure(code=proto_types.Failure_Other, message="Signing aborted")) self.iface.layout.show_send_tx(to_spend - change_amount - fee, coin) ret = yield proto.ButtonRequest(code=proto_types.ButtonRequest_SignTx) if not isinstance(ret, proto.ButtonAck): raise Exception(proto.Failure(code=proto_types.Failure_Other, message="Signing aborted")) # Serialize outputs for o2 in range(msg.outputs_count): # Request O req = proto.TxRequest(request_type=proto_types.TXOUTPUT, details=proto_types.TxRequestDetailsType(request_index=o2), serialized=proto_types.TxRequestSerializedType(serialized_tx=serialized_tx)) if o2 == 0: # Fill signature of last input req.serialized.signature = signature req.serialized.signature_index = i serialized_tx = '' ret2 = yield req out = ret2.tx.outputs[0] if len(list(out.address_n)) and out.HasField('address'): raise Exception(proto.Failure(code=proto_types.Failure_Other, message="Cannot have both address and address_n for the output")) # Calculate proper address for given address_n if len(list(out.address_n)): out.address = bip32.get_address(coin, list(out.address_n)) out.ClearField('address_n') serialized_tx += outtx.serialize_output(compile_TxOutput(out)) yield proto.TxRequest(request_type=proto_types.TXFINISHED, serialized=proto_types.TxRequestSerializedType(serialized_tx=serialized_tx))