Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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))