def test_int_to_hex(self): self.assertEqual('00', int_to_hex(0, 1)) self.assertEqual('ff', int_to_hex(-1, 1)) self.assertEqual('00000000', int_to_hex(0, 4)) self.assertEqual('01000000', int_to_hex(1, 4)) self.assertEqual('7f', int_to_hex(127, 1)) self.assertEqual('7f00', int_to_hex(127, 2)) self.assertEqual('80', int_to_hex(128, 1)) self.assertEqual('80', int_to_hex(-128, 1)) self.assertEqual('8000', int_to_hex(128, 2)) self.assertEqual('ff', int_to_hex(255, 1)) self.assertEqual('ff7f', int_to_hex(32767, 2)) self.assertEqual('0080', int_to_hex(-32768, 2)) self.assertEqual('ffff', int_to_hex(65535, 2)) with self.assertRaises(OverflowError): int_to_hex(256, 1) with self.assertRaises(OverflowError): int_to_hex(-129, 1) with self.assertRaises(OverflowError): int_to_hex(-257, 1) with self.assertRaises(OverflowError): int_to_hex(65536, 2) with self.assertRaises(OverflowError): int_to_hex(-32769, 2)
def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() inputs = [] inputsPaths = [] pubKeys = [] chipInputs = [] redeemScripts = [] signatures = [] preparedTrustedInputs = [] changePath = "" changeAmount = None output = None outputAmount = None p2shTransaction = False pin = "" self.get_client() # prompt for the PIN before displaying the dialog if necessary # Fetch inputs of the transaction to sign derivations = self.get_tx_derivations(tx) for txin in tx.inputs(): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] in ['p2sh']: p2shTransaction = True pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin) for i, x_pubkey in enumerate(x_pubkeys): if x_pubkey in derivations: signingPos = i s = derivations.get(x_pubkey) hwAddress = "{:s}/{:d}/{:d}".format(self.get_derivation()[2:], s[0], s[1]) break else: self.give_error("No matching x_key for sign_transaction") # should never happen redeemScript = Transaction.get_preimage_script(txin) inputs.append([txin['prev_tx'].raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos, txin.get('sequence', 0xffffffff - 1)]) inputsPaths.append(hwAddress) pubKeys.append(pubkeys) # Sanity check if p2shTransaction: for txin in tx.inputs(): if txin['type'] != 'p2sh': # should never happen self.give_error( "P2SH / regular input mixed in same transaction not supported") txOutput = var_int(len(tx.outputs())) for txout in tx.outputs(): output_type, addr, amount = txout txOutput += int_to_hex(amount, 8) script = tx.pay_script(addr) txOutput += var_int(len(script)//2) txOutput += script txOutput = bfh(txOutput) # Recognize outputs - only one output and one change is authorized if not p2shTransaction: for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if (info is not None) and (len(tx.outputs()) != 1): index, xpubs, m = info changePath = self.get_derivation()[2:] + "/{:d}/{:d}".format(*index) changeAmount = amount else: output = address outputAmount = amount self.handler.show_message(_("Confirm Transaction on your Ledger device...")) try: # Get trusted inputs from the original transactions for utxo in inputs: sequence = int_to_hex(utxo[5], 4) txtmp = bitcoinTransaction(bfh(utxo[0])) tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) tmp += txtmp.outputs[utxo[1]].amount chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) # Sign all inputs inputIndex = 0 rawTx = tx.serialize() self.get_client().enableAlternate2fa(False) self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs, redeemScripts[inputIndex]) outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() pin = self.handler.get_auth( outputData ) # the authenticate dialog and returns pin if not pin: raise UserWarning() if pin != 'paired': self.handler.show_message(_("Confirmed. Signing Transaction...")) while inputIndex < len(inputs): singleInput = [ chipInputs[inputIndex] ] self.get_client().startUntrustedTransaction( False, 0, singleInput, redeemScripts[inputIndex]) inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime, sighashType=tx.nHashType()) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BTChipException as e: if e.sw == 0x6985: # cancelled by user return else: logging.exception("") self.give_error(e, True) except BaseException as e: logging.exception("") self.give_error(e, True) finally: self.handler.finished() for i, txin in enumerate(tx.inputs()): signingPos = inputs[i][4] txin['signatures'][signingPos] = bh2u(signatures[i]) tx.raw = tx.serialize()
def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() inputs = [] inputsPaths = [] chipInputs = [] redeemScripts = [] signatures = [] preparedTrustedInputs = [] changePath = "" changeAmount = None output = None outputAmount = None pin = "" self.get_client() # prompt for the PIN before displaying the dialog if necessary # Sanity check is_p2sh = any(txin.type() == 'p2sh' for txin in tx.inputs) if is_p2sh and not all(txin.type() == 'p2sh' for txin in tx.inputs): self.give_error("P2SH / regular input mixed in same transaction not supported") # Fetch inputs of the transaction to sign derivations = self.get_tx_derivations(tx) for txin in tx.inputs: for i, x_pubkey in enumerate(txin.x_pubkeys): if x_pubkey.to_hex() in derivations: signingPos = i s = derivations.get(x_pubkey.to_hex()) hwAddress = "{:s}/{:d}/{:d}".format(self.get_derivation()[2:], s[0], s[1]) break else: self.give_error("No matching x_key for sign_transaction") # should never happen redeemScript = Transaction.get_preimage_script(txin) inputs.append([txin.value, txin.prev_idx, redeemScript, txin.prev_hash, signingPos, txin.sequence]) inputsPaths.append(hwAddress) # Concatenate all the tx outputs as binary txOutput = pack_list(tx.outputs, TxOutput.to_bytes) # Recognize outputs - only one output and one change is authorized if not is_p2sh: for tx_output, info in zip(tx.outputs, tx.output_info): if (info is not None) and len(tx.outputs) != 1: index, xpubs, m = info changePath = self.get_derivation()[2:] + "/{:d}/{:d}".format(*index) changeAmount = tx_output.value else: output = classify_tx_output(tx_output) outputAmount = tx_output.value self.handler.show_message(_("Confirm Transaction on your Ledger device...")) try: for utxo in inputs: sequence = int_to_hex(utxo[5], 4) chipInputs.append({'value' : utxo[0], 'witness' : True, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) # Sign all inputs inputIndex = 0 rawTx = tx.serialize() self.get_client().enableAlternate2fa(False) self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs, redeemScripts[inputIndex]) outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output.to_string(coin=Net.COIN) self.handler.finished() pin = self.handler.get_auth( outputData ) # the authenticate dialog and returns pin if not pin: raise UserWarning() self.handler.show_message(_("Confirmed. Signing Transaction...")) while inputIndex < len(inputs): singleInput = [ chipInputs[inputIndex] ] self.get_client().startUntrustedTransaction( False, 0, singleInput, redeemScripts[inputIndex]) inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime, sighashType=tx.nHashType()) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BTChipException as e: if e.sw == 0x6985: # cancelled by user return else: logger.exception("") self.give_error(e, True) except Exception as e: logger.exception("") self.give_error(e, True) finally: self.handler.finished() for txin, input, signature in zip(tx.inputs, inputs, signatures): txin.signatures[input[4]] = signature tx.raw = tx.serialize()
def sign_transaction(self, tx: Transaction, password: str, tx_context: TransactionContext) -> None: if tx.is_complete(): return assert self.handler is not None client = self.get_client() inputs: List[YInput] = [] inputsPaths = [] chipInputs = [] redeemScripts = [] signatures = [] changePath = "" changeAmount = None output = None outputAmount = None pin = "" self.get_client( ) # prompt for the PIN before displaying the dialog if necessary # Fetch inputs of the transaction to sign foundP2SHSpend = False allSpendsAreP2SH = True for txin in tx.inputs: foundP2SHSpend = foundP2SHSpend or txin.type( ) == ScriptType.MULTISIG_P2SH allSpendsAreP2SH = allSpendsAreP2SH and txin.type( ) == ScriptType.MULTISIG_P2SH for i, x_pubkey in enumerate(txin.x_pubkeys): if self.is_signature_candidate(x_pubkey): txin_xpub_idx = i inputPath = "%s/%d/%d" % (self.get_derivation()[2:], *x_pubkey.bip32_path()) break else: self.give_error("No matching x_key for sign_transaction" ) # should never happen inputs.append( YInput(txin.value, Transaction.get_preimage_script_bytes(txin), txin_xpub_idx, txin.sequence)) inputsPaths.append(inputPath) # Sanity check if foundP2SHSpend and not allSpendsAreP2SH: self.give_error( "P2SH / regular input mixed in same transaction not supported") # Concatenate all the tx outputs as binary txOutput = pack_list(tx.outputs, XTxOutput.to_bytes) # Recognize outputs - only one output and one change is authorized if not foundP2SHSpend: keystore_fingerprint = self.get_fingerprint() assert tx.output_info is not None for tx_output, output_metadatas in zip(tx.outputs, tx.output_info): info = output_metadatas.get(keystore_fingerprint) if (info is not None) and len(tx.outputs) != 1: key_derivation, xpubs, m = info key_subpath = compose_chain_string(key_derivation)[1:] changePath = self.get_derivation()[2:] + key_subpath changeAmount = tx_output.value else: output = classify_tx_output(tx_output) outputAmount = tx_output.value self.handler.show_message( _("Confirm Transaction on your Ledger device...")) try: for i, utxo in enumerate(inputs): txin = tx.inputs[i] sequence = int_to_hex(utxo.sequence, 4) prevout_bytes = txin.prevout_bytes() value_bytes = prevout_bytes + pack_le_int64(utxo.value) chipInputs.append({ 'value': value_bytes, 'witness': True, 'sequence': sequence }) redeemScripts.append(utxo.script_sig) # Sign all inputs inputIndex = 0 rawTx = tx.serialize() self.get_client().enableAlternate2fa(False) self.get_client().startUntrustedTransaction( True, inputIndex, chipInputs, redeemScripts[inputIndex]) outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = cast(ScriptTemplate, output).to_string() self.handler.finished() # the authenticate dialog and returns pin auth_pin = self.handler.get_auth(self, outputData) if not auth_pin: raise UserWarning() pin = auth_pin self.handler.show_message( _("Confirmed. Signing Transaction...")) while inputIndex < len(inputs): singleInput = [chipInputs[inputIndex]] self.get_client().startUntrustedTransaction( False, 0, singleInput, redeemScripts[inputIndex]) inputSignature = self.get_client().untrustedHashSign( inputsPaths[inputIndex], pin, lockTime=tx.locktime, sighashType=tx.nHashType()) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BTChipException as e: if e.sw == 0x6985: # cancelled by user return else: logger.exception("") self.give_error(e, True) except Exception as e: logger.exception("") self.give_error(e, True) finally: self.handler.finished() for txin, input, signature in zip(tx.inputs, inputs, signatures): txin.signatures[input.txin_xpub_idx] = signature
def sign_transaction(self, tx, password): if tx.is_complete(): return try: p2pkhTransaction = True derivations = self.get_tx_derivations(tx) inputhasharray = [] hasharray = [] pubkeyarray = [] # Build hasharray from inputs for i, txin in enumerate(tx.inputs()): if txin['type'] == 'coinbase': self.give_error( "Coinbase not supported") # should never happen if txin['type'] != 'p2pkh': p2pkhTransaction = False for x_pubkey in txin['x_pubkeys']: if x_pubkey in derivations: index = derivations.get(x_pubkey) inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1]) inputHash = Hash( binascii.unhexlify(tx.serialize_preimage(i))) hasharray_i = { 'hash': to_hexstr(inputHash), 'keypath': inputPath } hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction" ) # should never happen # Build pubkeyarray from outputs for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if info is not None: index, xpubs, m = info changePath = self.get_derivation() + "/%d/%d" % index changePubkey = self.derive_pubkey(index[0], index[1]) pubkeyarray_i = { 'pubkey': changePubkey, 'keypath': changePath } pubkeyarray.append(pubkeyarray_i) # Special serialization of the unsigned transaction for # the mobile verification app. # At the moment, verification only works for p2pkh transactions. if p2pkhTransaction: class CustomTXSerialization(Transaction): @classmethod def input_script(self, txin, estimate_size=False): if txin['type'] == 'p2pkh': return Transaction.get_preimage_script(txin) if txin['type'] == 'p2sh': # Multisig verification has partial support, but is # disabled. This is the expected serialization though, so we # leave it here until we activate it. return '00' + push_script( Transaction.get_preimage_script(txin)) raise Exception("unsupported type %s" % txin['type']) tx_dbb_serialized = CustomTXSerialization( tx.serialize()).serialize() else: # We only need this for the signing echo / verification. tx_dbb_serialized = None # Build sign command dbb_signatures = [] steps = math.ceil(1.0 * len(hasharray) / self.maxInputs) for step in range(int(steps)): hashes = hasharray[step * self.maxInputs:(step + 1) * self.maxInputs] msg = { "sign": { "data": hashes, "checkpub": pubkeyarray, }, } if tx_dbb_serialized is not None: msg["sign"]["meta"] = to_hexstr(Hash(tx_dbb_serialized)) msg = json.dumps(msg).encode('ascii') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception("Could not sign transaction.") reply = dbb_client.hid_send_encrypt(msg) if 'error' in reply: raise Exception(reply['error']['message']) if 'echo' not in reply: raise Exception("Could not sign transaction.") if self.plugin.is_mobile_paired( ) and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) if steps > 1: self.handler.show_message( _("Signing large transaction. Please be patient ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + " " + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." ) + "\n\n") else: self.handler.show_message( _("Signing transaction...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." )) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: if reply["error"].get('code') in (600, 601): # aborted via LED short touch or timeout raise UserCancelled() raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception("Could not sign transaction.") dbb_signatures.extend(reply['sign']) # Fill signatures if len(dbb_signatures) != len(tx.inputs()): raise Exception("Incorrect number of transactions signed." ) # Should never occur for i, txin in enumerate(tx.inputs()): num = txin['num_sig'] for pubkey in txin['pubkeys']: signatures = [sig for sig in txin['signatures'] if sig] if len(signatures) == num: break # txin is complete ii = txin['pubkeys'].index(pubkey) signed = dbb_signatures[i] if 'recid' in signed: # firmware > v2.1.1 recid = int(signed['recid'], 16) s = binascii.unhexlify(signed['sig']) h = inputhasharray[i] pk = MyVerifyingKey.from_signature(s, recid, h, curve=SECP256k1) pk = to_hexstr(point_to_ser(pk.pubkey.point, True)) elif 'pubkey' in signed: # firmware <= v2.1.1 pk = signed['pubkey'] if pk != pubkey: continue sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) sig = sigencode_der(sig_r, sig_s, generator_secp256k1.order()) txin['signatures'][ii] = ( to_hexstr(sig) + int_to_hex(Transaction.nHashType() & 255, 1)) tx._inputs[i] = txin except UserCancelled: raise except BaseException as e: self.give_error(e, True) else: logger.debug("Transaction is_complete %s", tx.is_complete()) tx.raw = tx.serialize()