def get_tx(self, txhash): # method moved from TxApiInsight from which this class is derived. Reason: an error of casting vout['value'] # to Decimal in the original method which occurres in some circumstances and causes the original value to be # distorted after the cast data = self.fetch_json('tx', txhash) t = trezor_proto.TransactionType() t.version = data['version'] t.lock_time = data['locktime'] for vin in data['vin']: i = t._add_inputs() if 'coinbase' in vin.keys(): i.prev_hash = b"\0" * 32 i.prev_index = 0xffffffff # signed int -1 i.script_sig = binascii.unhexlify(vin['coinbase']) i.sequence = vin['sequence'] else: i.prev_hash = binascii.unhexlify(vin['txid']) i.prev_index = vin['vout'] i.script_sig = binascii.unhexlify(vin['scriptSig']['hex']) i.sequence = vin['sequence'] for vout in data['vout']: o = t._add_bin_outputs() o.amount = round(Decimal(vout['value'] * 100000000)) # fixed here o.script_pubkey = binascii.unhexlify(vout['scriptPubKey']['hex']) return t
def get_tx(self, txhash): tx = None for psbt_in in self.psbt.inputs: if psbt_in.non_witness_utxo and psbt_in.non_witness_utxo.sha256 == uint256_from_str( binascii.unhexlify(txhash)[::-1]): tx = psbt_in.non_witness_utxo if not tx: raise ValueError("TX {} not found in PSBT".format(txhash)) t = proto.TransactionType() t.version = tx.nVersion t.lock_time = tx.nLockTime for vin in tx.vin: i = t._add_inputs() i.prev_hash = ser_uint256(vin.prevout.hash)[::-1] i.prev_index = vin.prevout.n i.script_sig = vin.scriptSig i.sequence = vin.nSequence for vout in tx.vout: o = t._add_bin_outputs() o.amount = vout.nValue o.script_pubkey = vout.scriptPubKey return t
def json_to_tx(self, jtx): t = trezor_proto.TransactionType() t.version = jtx["version"] t.lock_time = jtx["locktime"] t.inputs = [self.json_to_input(input) for input in jtx["vin"]] t.bin_outputs = [self.json_to_bin_output(output) for output in jtx["vout"]] return t
def make_transaction(tx) -> m.TransactionType: return m.TransactionType( version=tx.version, inputs=[make_input(txi) for txi in tx.inputs], outputs=[make_binoutput(txo) for txo in tx.outputs], lock_time=tx.lock_time, )
def copy_tx_meta(tx): tx_copy = trezor_proto.TransactionType(**tx) # clear fields tx_copy.inputs_cnt = len(tx.inputs) tx_copy.inputs = [] tx_copy.outputs_cnt = len(tx.bin_outputs or tx.outputs) tx_copy.outputs = [] tx_copy.bin_outputs = [] tx_copy.extra_data_len = len(tx.extra_data or b"") tx_copy.extra_data = None return tx_copy
def json_to_tx(coin, data): t = messages.TransactionType() t.version = data["version"] t.lock_time = data.get("locktime") if coin["decred"]: t.expiry = data["expiry"] t.inputs = [_json_to_input(coin, vin) for vin in data["vin"]] t.bin_outputs = [_json_to_bin_output(coin, vout) for vout in data["vout"]] # zcash extra data if is_zcash(coin) and t.version >= 2: joinsplit_cnt = len(data["vjoinsplit"]) if joinsplit_cnt == 0: t.extra_data = b"\x00" elif joinsplit_cnt >= 253: # we assume cnt < 253, so we can treat varIntLen(cnt) as 1 raise ValueError("Too many joinsplits") elif "hex" not in data: raise ValueError( "Raw TX data required for Zcash joinsplit transaction") else: rawtx = bytes.fromhex(data["hex"]) extra_data_len = 1 + joinsplit_cnt * 1802 + 32 + 64 t.extra_data = rawtx[-extra_data_len:] if is_polis(coin): dip2_type = data.get("type", 0) if t.version == 3 and dip2_type != 0: # It's a DIP2 special TX with payload if "extraPayloadSize" not in data or "extraPayload" not in data: raise ValueError("Payload data missing in DIP2 transaction") if data["extraPayloadSize"] * 2 != len(data["extraPayload"]): raise ValueError( "extra_data_len (%d) does not match calculated length (%d)" % (data["extraPayloadSize"], len(data["extraPayload"]) * 2)) t.extra_data = polis_utils.num_to_varint( data["extraPayloadSize"]) + bytes.fromhex(data["extraPayload"]) # Trezor firmware doesn't understand the split of version and type, so let's mimic the # old serialization format t.version |= dip2_type << 16 return t
def forge_prevtx( vouts: Sequence[Tuple[str, int]], network: str = "mainnet") -> Tuple[bytes, messages.TransactionType]: """ Forge a transaction with the given vouts. """ bitcoin.SelectParams(network) input = messages.TxInputType( prev_hash=b"\x00" * 32, prev_index=0xFFFFFFFF, script_sig=b"\x00", sequence=0xFFFFFFFF, ) outputs = [ messages.TxOutputBinType( amount=amount, script_pubkey=bytes(CBitcoinAddress(address).to_scriptPubKey()), ) for address, amount in vouts ] tx = messages.TransactionType( version=1, inputs=[input], bin_outputs=outputs, lock_time=0, ) cin = CTxIn( COutPoint(input.prev_hash, input.prev_index), CScript(input.script_sig), input.sequence, ) couts = [ CTxOut(output.amount, CScript(output.script_pubkey)) for output in tx.bin_outputs ] txhash = CTransaction([cin], couts, tx.lock_time, tx.version).GetTxid()[::-1] bitcoin.SelectParams("mainnet") return txhash, tx
def sign_tx(self, tx): # Get this devices master key fingerprint master_key = btc.get_public_node(self.client, [0]) master_fp = get_xpub_fingerprint(master_key.xpub) # Do multiple passes for multisig passes = 1 p = 0 while p < passes: # Prepare inputs inputs = [] to_ignore = [ ] # Note down which inputs whose signatures we're going to ignore for input_num, (psbt_in, txin) in py_enumerate( list(zip(tx.inputs, tx.tx.vin))): txinputtype = proto.TxInputType() # Set the input stuff txinputtype.prev_hash = ser_uint256(txin.prevout.hash)[::-1] txinputtype.prev_index = txin.prevout.n txinputtype.sequence = txin.nSequence # Detrermine spend type scriptcode = b'' if psbt_in.non_witness_utxo: utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n] txinputtype.script_type = proto.InputScriptType.SPENDADDRESS scriptcode = utxo.scriptPubKey txinputtype.amount = psbt_in.non_witness_utxo.vout[ txin.prevout.n].nValue elif psbt_in.witness_utxo: utxo = psbt_in.witness_utxo # Check if the output is p2sh if psbt_in.witness_utxo.is_p2sh(): txinputtype.script_type = proto.InputScriptType.SPENDP2SHWITNESS else: txinputtype.script_type = proto.InputScriptType.SPENDWITNESS scriptcode = psbt_in.witness_utxo.scriptPubKey txinputtype.amount = psbt_in.witness_utxo.nValue # Set the script if psbt_in.witness_script: scriptcode = psbt_in.witness_script elif psbt_in.redeem_script: scriptcode = psbt_in.redeem_script def ignore_input(): txinputtype.address_n = [0x80000000] inputs.append(txinputtype) to_ignore.append(input_num) # Check for multisig is_ms, multisig = parse_multisig(scriptcode) if is_ms: # Add to txinputtype txinputtype.multisig = multisig if psbt_in.non_witness_utxo: if utxo.is_p2sh: txinputtype.script_type = proto.InputScriptType.SPENDMULTISIG else: # Cannot sign bare multisig, ignore it ignore_input() continue elif not is_ms and psbt_in.non_witness_utxo and not utxo.is_p2pkh: # Cannot sign unknown spk, ignore it ignore_input() continue elif not is_ms and psbt_in.witness_utxo and psbt_in.witness_script: # Cannot sign unknown witness script, ignore it ignore_input() continue # Find key to sign with found = False our_keys = 0 for key in psbt_in.hd_keypaths.keys(): keypath = psbt_in.hd_keypaths[key] if keypath[ 0] == master_fp and key not in psbt_in.partial_sigs: if not found: txinputtype.address_n = keypath[1:] found = True our_keys += 1 # Determine if we need to do more passes to sign everything if our_keys > passes: passes = our_keys if not found: # This input is not one of ours ignore_input() continue # append to inputs inputs.append(txinputtype) # address version byte if self.is_testnet: p2pkh_version = b'\x6f' p2sh_version = b'\xc4' bech32_hrp = 'tb' else: p2pkh_version = b'\x00' p2sh_version = b'\x05' bech32_hrp = 'bc' # prepare outputs outputs = [] for out in tx.tx.vout: txoutput = proto.TxOutputType() txoutput.amount = out.nValue txoutput.script_type = proto.OutputScriptType.PAYTOADDRESS if out.is_p2pkh(): txoutput.address = to_address(out.scriptPubKey[3:23], p2pkh_version) elif out.is_p2sh(): txoutput.address = to_address(out.scriptPubKey[2:22], p2sh_version) else: wit, ver, prog = out.is_witness() if wit: txoutput.address = bech32.encode(bech32_hrp, ver, prog) else: raise TypeError("Output is not an address") # append to outputs outputs.append(txoutput) # Prepare prev txs prevtxs = {} for psbt_in in tx.inputs: if psbt_in.non_witness_utxo: prev = psbt_in.non_witness_utxo t = proto.TransactionType() t.version = prev.nVersion t.lock_time = prev.nLockTime for vin in prev.vin: i = proto.TxInputType() i.prev_hash = ser_uint256(vin.prevout.hash)[::-1] i.prev_index = vin.prevout.n i.script_sig = vin.scriptSig i.sequence = vin.nSequence t.inputs.append(i) for vout in prev.vout: o = proto.TxOutputBinType() o.amount = vout.nValue o.script_pubkey = vout.scriptPubKey t.bin_outputs.append(o) logging.debug(psbt_in.non_witness_utxo.hash) prevtxs[ser_uint256( psbt_in.non_witness_utxo.sha256)[::-1]] = t # Sign the transaction tx_details = proto.SignTx() tx_details.version = tx.tx.nVersion tx_details.lock_time = tx.tx.nLockTime if self.is_testnet: signed_tx = btc.sign_tx(self.client, "Testnet", inputs, outputs, tx_details, prevtxs) else: signed_tx = btc.sign_tx(self.client, "Bitcoin", inputs, outputs, tx_details, prevtxs) # Each input has one signature for input_num, (psbt_in, sig) in py_enumerate( list(zip(tx.inputs, signed_tx[0]))): if input_num in to_ignore: continue for pubkey in psbt_in.hd_keypaths.keys(): fp = psbt_in.hd_keypaths[pubkey][0] if fp == master_fp and pubkey not in psbt_in.partial_sigs: psbt_in.partial_sigs[pubkey] = sig + b'\x01' break p += 1 return {'psbt': tx.serialize()}
def sign_tx(sig_percent, client, coin_name, inputs, outputs, details=None, prev_txes=None): # set up a transactions dict txes = {None: trezor_proto.TransactionType(inputs=inputs, outputs=outputs)} # preload all relevant transactions ahead of time for inp in inputs: if inp.script_type not in ( trezor_proto.InputScriptType.SPENDP2SHWITNESS, trezor_proto.InputScriptType.SPENDWITNESS, trezor_proto.InputScriptType.EXTERNAL, ): try: prev_tx = prev_txes[inp.prev_hash] except Exception as e: raise ValueError("Could not retrieve prev_tx") from e if not isinstance(prev_tx, trezor_proto.TransactionType): raise ValueError("Invalid value for prev_tx") from None txes[inp.prev_hash] = prev_tx if details is None: signtx = trezor_proto.SignTx() else: signtx = details signtx.coin_name = coin_name signtx.inputs_count = len(inputs) signtx.outputs_count = len(outputs) res = client.call(signtx) # Prepare structure for signatures signatures = [None] * len(inputs) serialized_tx = b"" def copy_tx_meta(tx): tx_copy = trezor_proto.TransactionType(**tx) # clear fields tx_copy.inputs_cnt = len(tx.inputs) tx_copy.inputs = [] tx_copy.outputs_cnt = len(tx.bin_outputs or tx.outputs) tx_copy.outputs = [] tx_copy.bin_outputs = [] tx_copy.extra_data_len = len(tx.extra_data or b"") tx_copy.extra_data = None return tx_copy R = trezor_proto.RequestType percent = 0 # Used for signaling progress. 1-10 for inputs/outputs, 10-100 for sigs. sig_percent.emit(percent) while isinstance(res, trezor_proto.TxRequest): # If there's some part of signed transaction, let's add it if res.serialized: if res.serialized.serialized_tx: serialized_tx += res.serialized.serialized_tx if res.serialized.signature_index is not None: idx = res.serialized.signature_index sig = res.serialized.signature if signatures[idx] is not None: raise ValueError("Signature for index %d already filled" % idx) signatures[idx] = sig # emit completion percent percent = 10 + int(90 * (idx + 1) / len(signatures)) sig_percent.emit(percent) if res.request_type == R.TXFINISHED: break # Device asked for one more information, let's process it. current_tx = txes[res.details.tx_hash] if res.request_type == R.TXMETA: msg = copy_tx_meta(current_tx) res = client.call(trezor_proto.TxAck(tx=msg)) elif res.request_type == R.TXINPUT: if percent == 0 or (res.details.request_index > 0 and percent < 10): percent = 1 + int( 8 * (res.details.request_index + 1) / len(inputs)) sig_percent.emit(percent) msg = trezor_proto.TransactionType() msg.inputs = [current_tx.inputs[res.details.request_index]] res = client.call(trezor_proto.TxAck(tx=msg)) elif res.request_type == R.TXOUTPUT: # Update just one percent then display additional waiting message (emitting -1) if percent == 9: percent += 1 sig_percent.emit(percent) sig_percent.emit(-1) msg = trezor_proto.TransactionType() if res.details.tx_hash: msg.bin_outputs = [ current_tx.bin_outputs[res.details.request_index] ] else: msg.outputs = [current_tx.outputs[res.details.request_index]] res = client.call(trezor_proto.TxAck(tx=msg)) elif res.request_type == R.TXEXTRADATA: o, l = res.details.extra_data_offset, res.details.extra_data_len msg = trezor_proto.TransactionType() msg.extra_data = current_tx.extra_data[o:o + l] res = client.call(trezor_proto.TxAck(tx=msg)) if isinstance(res, trezor_proto.Failure): raise Exception("Signing failed") if not isinstance(res, trezor_proto.TxRequest): raise Exception("Unexpected message") if None in signatures: raise RuntimeError("Some signatures are missing!") return signatures, serialized_tx
def json_to_tx(coin, data): t = messages.TransactionType() t.version = data["version"] t.lock_time = data.get("locktime") if coin["decred"]: t.expiry = data["expiry"] t.inputs = [] for vin in data["vin"]: if "scriptSig" in vin and vin["scriptSig"]["hex"] == "c9": i = messages.TxInputType() i.prev_hash = b"\0" * 32 i.prev_index = vin["sequence"] i.script_sig = bytes.fromhex(vin["scriptSig"]["hex"]) i.sequence = vin["sequence"] t.inputs.append(i) else: t.inputs.append(_json_to_input(coin, vin)) t.bin_outputs = [_json_to_bin_output(coin, vout) for vout in data["vout"]] # zcash extra data if is_zcash(coin) and t.version >= 2: joinsplit_cnt = len(data["vjoinsplit"]) if joinsplit_cnt == 0: t.extra_data = b"\x00" elif joinsplit_cnt >= 253: # we assume cnt < 253, so we can treat varIntLen(cnt) as 1 raise ValueError("Too many joinsplits") elif "hex" not in data: raise ValueError( "Raw TX data required for Zcash joinsplit transaction") else: rawtx = bytes.fromhex(data["hex"]) extra_data_len = 1 + joinsplit_cnt * 1802 + 32 + 64 t.extra_data = rawtx[-extra_data_len:] if is_extra_payload(coin): dip2_type = data.get("type", 0) if t.version == 3 and dip2_type != 0: # It's a DIP2 special TX with payload if dip2_type == DashTxType.SPEC_CB_TX: data["extraPayload"] = serialize_cbTx(data) elif dip2_type == DashTxType.LELANTUS_JSPLIT: data["extraPayload"] = serialize_Lelantus(data) else: raise NotImplementedError( "Only spending of V3 coinbase outputs has been inplemented. " "Please file an issue at https://github.com/firoorg/firo-masternode-tool/issues containing " "the tx type=" + str(dip2_type)) data["extraPayloadSize"] = len(data["extraPayload"]) >> 1 if "extraPayloadSize" not in data or "extraPayload" not in data: raise ValueError("Payload data missing in DIP2 transaction") if data["extraPayloadSize"] * 2 != len(data["extraPayload"]): raise ValueError( "extra_data_len (%d) does not match calculated length (%d)" % (data["extraPayloadSize"], len(data["extraPayload"]) * 2)) t.extra_data = dash_utils.num_to_varint( data["extraPayloadSize"]) + bytes.fromhex(data["extraPayload"]) # Trezor firmware doesn't understand the split of version and type, so let's mimic the # old serialization format t.version |= dip2_type << 16 return t