def _get_address(self, coin, address_n, multisig): if multisig: # check if we own the pubkey pubkey = BIP32(self.storage.get_node()).get_public_node(address_n).public_key try: pubkeys = [ public_ckd(n.node, list(n.address_n)).public_key for n in multisig.pubkeys ] sig_index = list(pubkeys).index(pubkey) except ValueError: return proto.Failure(code=proto_types.Failure_Other, message="Pubkey not found in multisig script") # convert script to P2SH address script = transaction.compile_script_multisig(multisig) h160 = tools.hash_160(script) address = tools.hash_160_to_bc_address(h160, coin.address_type_p2sh) else: address = BIP32(self.storage.get_node()).get_address(coin, address_n) self.layout.show_receiving_address(address) self.custom_message = True # Yes button will redraw screen return proto.Address(address=address)
def compile_script_multisig(multisig): # P2SH def n_to_op(n): if n < 1 or n > 15: raise Exception("Unsupported multisig type") return chr(0x50 + n) n = len(multisig.pubkeys) m = multisig.m script = n_to_op(m) for node in multisig.pubkeys: pubkey = public_ckd(node.node, list(node.address_n)).public_key script += op_push(len(pubkey)) script += pubkey script += n_to_op(n) script += '\xae' # OP_CHECKMULTISIG return script
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) tx_multisig_fingerprint = None # 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: multisig_fp = None 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 ret2.tx.inputs[0].HasField('multisig'): fp = multisig_fingerprint(ret2.tx.inputs[0].multisig) if multisig_fp == None: multisig_fp = fp else: if multisig_fp != fp: multisig_fp = '' # 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) if ret2.tx.inputs[0].script_type == proto_types.SPENDMULTISIG: sign.serialize_input_multisig(ret2.tx.inputs[0], ret2.tx.inputs[0].multisig, secexp) else: 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")) is_change = False # 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') is_change = True if out.HasField('multisig'): if multisig_fp != None and multisig_fp != '' and \ multisig_fingerprint(out.multisig) == multisig_fp: is_change = True if is_change: if out_change == None: 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 # confirm outputs (if not change and not op_return) if out_change != o2 and out.script_type != proto_types.PAYTOOPRETURN: 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() if inp.script_type == proto_types.SPENDMULTISIG: # We're doing partial signature for multisig input signatures = inp.multisig.signatures # Find position of actual signature in 'signatures' list try: pubkeys = [ public_ckd(n.node, list(n.address_n)).public_key for n in inp.multisig.pubkeys ] sig_index = list(pubkeys).index(pubkey) except ValueError: raise Exception(proto.Failure(code=proto_types.Failure_Other, message="Pubkey not found in multisig script")) # Put signature to proper place signatures[sig_index] = signature serialized_tx += outtx.serialize_input_multisig(inp, signatures, inp.multisig) else: # Standard paytoaddress input 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, 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))