def transact(self, transaction, out_f): print("Sending transaction to trezor for signature...\n", file=out_f) signature = self.client.ethereum_sign_tx( n=[ 44 + bip32utils.BIP32_HARDEN, 60 + bip32utils.BIP32_HARDEN, bip32utils.BIP32_HARDEN, 0, self.index ], nonce=transaction["nonce"], gas_price=transaction["gasPrice"], gas_limit=transaction["gas"], to=bytearray.fromhex(transaction["to"][2:]), value=transaction["value"], data=bytearray.fromhex(transaction["data"][2:])) transaction.pop("from") unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction) raw_transaction = encode_transaction(unsigned_transaction, vrs=(signature[0], int(signature[1].hex(), 16), int(signature[2].hex(), 16))) print("Submitting transaction...\n", file=out_f) txn_hash = self.w3.eth.sendRawTransaction(raw_transaction) # Wait for transaction to be mined receipt = None while receipt is None: time.sleep(1) receipt = self.w3.eth.getTransactionReceipt(txn_hash) return receipt
def _transact(self, function_call=None, transaction=None): """Transaction utility: builds a transaction and signs it with private key, or uses native transactions with accounts """ # If we have no local means to sign transactions, raise error if not (self._ledger or self._key_file or self._private_key): raise ValueError( "No valid signing method found!\n" "Please specify one of: key_file, private_key, auth_type='ledger' or auth_type='trezor'" ) private_key = self._private_key if self._key_file: password = self._password or getpass( "Password to decrypt keystore file %s: " % self.account) private_key = self._get_private_key(self._key_file, password) # get the nonce for the current address nonce = self.w3.eth.getTransactionCount(self.address) # Build a transaction to sign gas_buffer = 100000 # Estimate gas cost if transaction is None: transaction = {} transaction.update({ 'nonce': nonce, 'gas': 4000000, 'gasPrice': self.w3.eth.generateGasPrice() }) if function_call: transaction = function_call.buildTransaction(transaction) gas_limit = self.w3.eth.estimateGas(transaction) + gas_buffer transaction.update({'gas': gas_limit}) if 'from' in transaction: del transaction['from'] if private_key: # Sign the transaction using the private key and send it as raw transaction signed_tx = self.w3.eth.account.signTransaction( transaction, '0x' + private_key) tx_hash = self.w3.eth.sendRawTransaction(signed_tx.rawTransaction) else: unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction) pos = self._ledger.get_account_index(self.address) (v, r, s) = self._ledger.sign(rlp.encode(unsigned_transaction), account_index=pos) encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s)) tx_hash = self.w3.eth.sendRawTransaction(encoded_transaction) self.logger.info( "Submitted transaction %s, waiting for transaction receipt..." % tx_hash.hex()) tx_receipt = self._wait_for_receipt(tx_hash) return hasattr(tx_receipt, "status") and tx_receipt["status"] == 1
def _sign_web3_transaction(tx: Dict[str, any], v: int, r: int, s: int) -> (bytes, HexBytes): """ Signed transaction can be send with w3.eth.sendRawTransaction """ unsigned_transaction = serializable_unsigned_transaction_from_dict(tx) rlp_encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s)) # To get the address signing, just do ecrecover_to_pub(unsigned_transaction.hash(), v, r, s) return rlp_encoded_transaction, unsigned_transaction.hash()
def transact(self, transaction, out_f): tx = UnsignedTransaction( nonce=transaction["nonce"], gasPrice=transaction["gasPrice"], gas=transaction["gas"], to=bytes(bytearray.fromhex(transaction["to"][2:])), value=transaction["value"], data=bytes(bytearray.fromhex(transaction["data"][2:]))) encoded_tx = rlp.encode(tx, UnsignedTransaction) overflow = len(self.dongle_path) + 1 + len(encoded_tx) - 255 if overflow > 0: encoded_tx, remaining_tx = encoded_tx[:-overflow], encoded_tx[ -overflow:] apdu = LedgerIdentityProvider.SIGN_TX_OP apdu += bytearray([ len(self.dongle_path) + 1 + len(encoded_tx), int(len(self.dongle_path) / 4) ]) apdu += self.dongle_path + encoded_tx try: print("Sending transaction to ledger for signature...\n", file=out_f) result = self.dongle.exchange(apdu) while overflow > 0: encoded_tx = remaining_tx overflow = len(encoded_tx) - 255 if overflow > 0: encoded_tx, remaining_tx = encoded_tx[: -overflow], encoded_tx[ -overflow:] apdu = LedgerIdentityProvider.SIGN_TX_OP_CONT apdu += bytearray([len(encoded_tx)]) apdu += encoded_tx result = self.dongle.exchange(apdu) except CommException: raise RuntimeError( "Received commException from ledger. Are you sure your device is unlocked and the " "Ethereum app is running?") transaction.pop("from") unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction) raw_transaction = encode_transaction( unsigned_transaction, vrs=(result[0], int.from_bytes(result[1:33], byteorder="big"), int.from_bytes(result[33:65], byteorder="big"))) return send_and_wait_for_transaction(raw_transaction, self.w3, out_f)
def transact(self, transaction, out_f): tx = UnsignedTransaction( nonce=transaction["nonce"], gasPrice=transaction["gasPrice"], gas=transaction["gas"], to=bytes(bytearray.fromhex(transaction["to"][2:])), value=transaction["value"], data=bytes(bytearray.fromhex(transaction["data"][2:])) ) encoded_tx = rlp.encode(tx, UnsignedTransaction) overflow = len(self.dongle_path) + 1 + len(encoded_tx) - 255 if overflow > 0: encoded_tx, remaining_tx = encoded_tx[:- overflow], encoded_tx[-overflow:] apdu = LedgerIdentityProvider.SIGN_TX_OP apdu += bytearray([len(self.dongle_path) + 1 + len(encoded_tx), int(len(self.dongle_path) / 4)]) apdu += self.dongle_path + encoded_tx try: print("Sending transaction to ledger for signature...\n", file=out_f) result = self.dongle.exchange(apdu) while overflow > 0: encoded_tx = remaining_tx overflow = len(encoded_tx) - 255 if overflow > 0: encoded_tx, remaining_tx = encoded_tx[:- overflow], encoded_tx[-overflow:] apdu = LedgerIdentityProvider.SIGN_TX_OP_CONT apdu += bytearray([len(encoded_tx)]) apdu += encoded_tx result = self.dongle.exchange(apdu) except CommException: raise RuntimeError("Received commException from ledger. Are you sure your device is unlocked and the " "Ethereum app is running?") transaction.pop("from") unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction) raw_transaction = encode_transaction(unsigned_transaction, vrs=(result[0], int.from_bytes( result[1:33], byteorder="big"), int.from_bytes(result[33:65], byteorder="big"))) return send_and_wait_for_transaction(raw_transaction, self.w3, out_f)
def sign_keyvault(client, vault_url, key_name, key_version, tx, chain_id=0): unsigned_tx = serializable_unsigned_transaction_from_dict(tx) unsigned_tx_hash = unsigned_tx.hash() key_bundle = client.get_key(vault_url, key_name, key_version) json_key = key_bundle.key pubkey = convert_json_key_to_public_key_bytes(json_key) address_signer = public_key_to_address(pubkey[1:]) sig_resp = client.sign(vault_url, key_name, key_version, 'ECDSA256', unsigned_tx_hash) vrs = convert_azure_secp256k1_signature_to_vrs(pubkey, unsigned_tx_hash, sig_resp.result, chain_id) ret_signed_transaction = encode_transaction(unsigned_tx, vrs) return address_signer, ret_signed_transaction
def transact(self, transaction, out_f): print("Sending transaction to trezor for signature...\n", file=out_f) signature = self.client.ethereum_sign_tx(n=[44 + BIP32_HARDEN, 60 + BIP32_HARDEN, BIP32_HARDEN, 0, self.index], nonce=transaction["nonce"], gas_price=transaction["gasPrice"], gas_limit=transaction["gas"], to=bytearray.fromhex(transaction["to"][2:]), value=transaction["value"], data=bytearray.fromhex(transaction["data"][2:])) transaction.pop("from") unsigned_transaction = serializable_unsigned_transaction_from_dict(transaction) raw_transaction = encode_transaction(unsigned_transaction, vrs=(signature[0], int(signature[1].hex(), 16), int(signature[2].hex(), 16))) return send_and_wait_for_transaction(raw_transaction, self.w3, out_f)
def transact(self, transaction, out_f): tx = UnsignedTransaction( nonce=transaction["nonce"], gasPrice=transaction["gasPrice"], gas=transaction["gas"], to=bytes(bytearray.fromhex(transaction["to"][2:])), value=transaction["value"], data=bytes(bytearray.fromhex(transaction["data"][2:]))) encoded_tx = rlp.encode(tx, UnsignedTransaction) apdu = LedgerIdentityProvider.SIGN_TX_OP apdu += bytearray([ len(self.dongle_path) + 1 + len(encoded_tx), int(len(self.dongle_path) / 4) ]) apdu += self.dongle_path + encoded_tx try: print("Sending transaction to ledger for signature...\n", file=out_f) result = self.dongle.exchange(apdu) except CommException: raise RuntimeError( "Received commException from ledger. Are you sure your device is unlocked and the " "Ethereum app is running?") transaction.pop("from") unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction) raw_transaction = encode_transaction( unsigned_transaction, vrs=(result[0], int.from_bytes(result[1:33], byteorder="big"), int.from_bytes(result[33:65], byteorder="big"))) print("Submitting transaction...\n", file=out_f) txn_hash = self.w3.eth.sendRawTransaction(raw_transaction) # Wait for transaction to be mined receipt = None while receipt is None: time.sleep(1) receipt = self.w3.eth.getTransactionReceipt(txn_hash) return receipt
def sign_keyvault(addressSigner, signingClient, vault_url, key_name, key_version, tx, chain_id=0): unsigned_tx = serializable_unsigned_transaction_from_dict(tx) unsigned_tx_hash = unsigned_tx.hash() valid = False while not valid: sig_resp = signingClient.sign(vault_url, key_name, key_version, 'ES256K', unsigned_tx_hash) v, r, s, valid = util.convert_azure_secp256k1_signature_to_vrs( pubkey, unsigned_tx_hash, sig_resp.result, chain_id) vrs = (v, r, s) ret_signed_transaction = encode_transaction(unsigned_tx, vrs) return address_signer, ret_signed_transaction
def transact(self, transaction, out_f): print("Sending transaction to trezor for signature...\n", file=out_f) signature = self.client.ethereum_sign_tx(n=[44 + BIP32_HARDEN, 60 + BIP32_HARDEN, BIP32_HARDEN, 0, self.index], nonce=transaction["nonce"], gas_price=transaction["gasPrice"], gas_limit=transaction["gas"], to=bytearray.fromhex( transaction["to"][2:]), value=transaction["value"], data=bytearray.fromhex(transaction["data"][2:])) transaction.pop("from") unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction) raw_transaction = encode_transaction(unsigned_transaction, vrs=(signature[0], int(signature[1].hex(), 16), int(signature[2].hex(), 16))) return send_and_wait_for_transaction(raw_transaction, self.w3, out_f)
def sign_transaction_dict(eth_key, transaction_dict): # generate RLP-serializable transaction, with defaults filled unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction_dict) transaction_hash = unsigned_transaction.hash() # detect chain if isinstance(unsigned_transaction, UnsignedTransaction): chain_id = None else: chain_id = unsigned_transaction.v # sign with private key (v, r, s) = sign_transaction_hash(eth_key, transaction_hash, chain_id) # serialize transaction with rlp encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s)) return (v, r, s, encoded_transaction)
def signTx(cls, tx): def int_to_big_endian(value): return value.to_bytes((value.bit_length() + 7) // 8, 'big') address_n = tools.parse_path(cls.hdpath()) msg = proto.EthereumSignTx(address_n=address_n, nonce=int_to_big_endian(tx['nonce']), gas_price=int_to_big_endian(tx['gasPrice']), gas_limit=int_to_big_endian(tx['gas']), chain_id=int(tx['chainId']), value=int_to_big_endian(tx['value'])) if tx['to']: msg.to = tx['to'] data = None if tx['data'].__class__ is str: data = bytes.fromhex(tx['data'].replace('0x', '')) elif tx['data'].__class__ is bytes: data = tx['data'] if data: msg.data_length = len(data) data, chunk = data[1024:], data[:1024] msg.data_initial_chunk = chunk try: response = cls.call_raw(msg, atomic=False) # Confused? Ask trezor why. I don't know why. # ButtonAck is a no-op afaict. But you still have to send it. # Punch the monkey. # Punch it. # Punch the monkey. # Punch the monkey. # Punch the monkey. while response.__class__.__name__ == 'ButtonRequest': response = cls.call_raw(proto.ButtonAck()) if response.__class__.__name__ == 'PinMatrixRequest': cls.matrix_request_window() raise SignTxError("Credstick needs to be unlocked") elif response.__class__.__name__ == 'PassphraseRequest': cls.passphrase_request_window() raise SignTxError("Credstick needs to be unlocked") elif response.__class__.__name__ == 'Failure': raise SignTxError except TransportException: raise SignTxError while response.data_length is not None: data_length = response.data_length data, chunk = data[data_length:], data[:data_length] response = cls.call_raw(proto.EthereumTxAck(data_chunk=chunk), atomic=False) # above, we were calling out with atomic=False to # prevent the session from being terminated. cls.transport.end_session() _v = response.signature_v _r = response.signature_r _s = response.signature_s sutx = serializable_unsigned_transaction_from_dict(tx) return cls.signed_tx(sutx, _v, int(_r.hex(), 16), int(_s.hex(), 16))
def signTx(cls, transaction_dict): apdu = None try: tx = serializable_unsigned_transaction_from_dict(transaction_dict) encodedTx = rlp.encode(tx) encodedPath = encode_path(cls.hdpath()) # Each path element is 4 bytes. How many path elements are we sending? derivationPathCount = (len(encodedPath) // 4).to_bytes(1, 'big') # Prepend the byte representing the count of path elements to the path encoding itself. encodedPath = derivationPathCount + encodedPath dataPayload = encodedPath + encodedTx # Big thanks to the Geth team for their ledger implementation (and documentation). # You guys are stars. # # To the others reading, the ledger can only take 255 bytes of data payload per apdu exchange. # hence, you have to chunk and use 0x08 for the P1 opcode on subsequent calls. p1_op = P1_FIRST_TRANS_DATA_BLOCK while len(dataPayload) > 0: chunkSize = 255 if chunkSize > len(dataPayload): chunkSize = len(dataPayload) encodedChunkSize = (chunkSize).to_bytes(1, 'big') apdu = CLA + INS_OPCODE_SIGN_TRANS + p1_op + P2_UNUSED_PARAMETER + encodedChunkSize + dataPayload[: chunkSize] ## Keeping this here commented in case I need to debug it again. #apdufile = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8)) #f = open('logs/{}'.format(apdufile), 'wb') #f.write(apdu) #f.close() result = cls._driver.exchange(apdu) dataPayload = dataPayload[chunkSize:] p1_op = P1_SUBSEQUENT_TRANS_DATA_BLOCK _v = result[0] _r = int((result[1:1 + 32]).hex(), 16) _s = int((result[1 + 32:1 + 32 + 32]).hex(), 16) stx = cls.signed_tx(tx, _v, _r, _s) except (CommException, BaseException) as e: if apdu: logging.debug( "Ledger device threw error {} while attempting SignTx with apdu {}" .format(e, apdu)) raise SignTxError( "Ledger device threw error {} while attempting SignTx with apdu {}" .format(e, apdu)) else: logging.debug( "Ledger device threw error {} while attempting SignTx". format(e)) raise SignTxError( "Ledger device threw error while attempting SignTx: {}". format(e)) return stx
def _transact(self, function_call=None, transaction=None): """Transaction utility: builds a transaction and signs it with private key, or uses native transactions with accounts """ # If we have no local means to sign transactions, raise error if not (self._ledger or self._key_file or self._private_key): raise ValueError( "No valid signing method found!\n" "Please specify one of: key_file, private_key, auth_type='ledger' or auth_type='trezor'" ) private_key = self._private_key if self._key_file: password = self._password or getpass( "Password to decrypt keystore file %s: " % self.account) private_key = self._get_private_key(self._key_file, password) # Build a transaction to sign gas_buffer = 100000 # Estimate gas cost if transaction is None: transaction = {} auto_gas_price = self.w3.eth.generateGasPrice() user_gas_price = transaction.get('gasPrice') transaction.update({ 'nonce': transaction.get("nonce", self.w3.eth.getTransactionCount(self.address)), 'gas': 4000000, 'gasPrice': auto_gas_price }) if function_call: transaction = function_call.buildTransaction(transaction) gas_limit = self.w3.eth.estimateGas(transaction) + gas_buffer transaction.update({ 'gas': gas_limit, 'gasPrice': user_gas_price if user_gas_price is not None else auto_gas_price }) if 'from' in transaction: del transaction['from'] if private_key: # Sign the transaction using the private key and send it as raw transaction signed_tx = self.w3.eth.account.signTransaction( transaction, '0x' + private_key) tx_hash = self.w3.eth.sendRawTransaction(signed_tx.rawTransaction) else: unsigned_transaction = serializable_unsigned_transaction_from_dict( transaction) pos = self._ledger.get_account_index(self.address) self.logger.info( "Signing transaction with your hardware wallet, please confirm on the hardware device when prompted..." ) (v, r, s) = self._ledger.sign(rlp.encode(unsigned_transaction), account_index=pos) encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s)) tx_hash = self.w3.eth.sendRawTransaction(encoded_transaction) self.logger.info( "Submitted transaction %s, waiting for transaction receipt..." % tx_hash.hex()) tx_receipt = None while not tx_receipt: try: tx_receipt = self._wait_for_receipt(tx_hash) except web3.utils.threads.Timeout: self.logger.warning( "Transaction still pending after 1 minute, waiting some more..." ) self.logger.info( "Gas used: %s at gas price of %.2f gwei (%.8f ether)" % (tx_receipt.get("gasUsed"), self.w3.fromWei(transaction.get("gasPrice"), 'gwei'), self.w3.fromWei( tx_receipt.get("gasUsed") * transaction.get("gasPrice"), 'ether'))) return hasattr(tx_receipt, "status") and tx_receipt["status"] == 1