Created
June 3, 2018 21:08
-
-
Save bargst/5f896e4a5984593d43f1b4eb66f79d68 to your computer and use it in GitHub Desktop.
Python Ledger Account list and sign
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Requirement: pip install ledgerblue | |
from ledgerblue.comm import getDongle | |
import struct | |
ETH_DERIVATION_PATH_PREFIX = "44'/60'/0'/" | |
class LedgerAccount: | |
""" | |
Ledger Ethereum App Protocol Spec is located at: | |
<https://github.com/LedgerHQ/blue-app-eth/blob/master/doc/ethapp.asc> | |
References: | |
- https://github.com/LedgerHQ/blue-app-eth/blob/master/getPublicKey.py | |
- https://github.com/bargst/pyethoff/blob/master/tx_sign.py | |
""" | |
def __init__(self): | |
self.dongle = getDongle(debug=False) | |
def _parse_bip32_path(self, offset): | |
""" | |
Convert an offset to a bytes payload to be sent to the ledger | |
representing bip32 derivation path. | |
""" | |
path = ETH_DERIVATION_PATH_PREFIX + str(offset) | |
result = bytes() | |
elements = path.split('/') | |
for pathElement in elements: | |
element = pathElement.split("'") | |
if len(element) == 1: | |
result = result + struct.pack(">I", int(element[0])) | |
else: | |
result = result + struct.pack(">I", 0x80000000 | int(element[0])) | |
return result | |
def get_address(self, offset): | |
""" | |
Query the ledger device for a public ethereum address. | |
Offset is the number in the HD wallet tree | |
""" | |
donglePath = self._parse_bip32_path(offset) | |
apdu = bytes.fromhex('e0020000') # https://github.com/LedgerHQ/blue-app-eth/blob/master/doc/ethapp.asc#get-eth-public-address | |
apdu += bytes([len(donglePath) + 1]) | |
apdu += bytes([len(donglePath) // 4]) | |
apdu += donglePath | |
result = self.dongle.exchange(apdu, timeout=60) | |
# Parse result | |
offset = 1 + result[0] | |
address = result[offset + 1 : offset + 1 + result[offset]] | |
return f'0x{address.decode()}' | |
def get_offset(self, address): | |
""" | |
Convert an address to the HD wallet tree offset | |
""" | |
offset = 0 | |
while address != self.get_address(offset): | |
offset += 1 | |
return offset | |
def list(self, limit=5, page=0): | |
""" | |
List Ethereum HD wallet adrress of the ledger device | |
""" | |
return list(map(lambda offset: self.get_address(offset), range(page*limit, (page+1)*limit))) | |
def sign(self, rlp_encoded_tx, offset=None, address=''): | |
""" | |
Sign an RLP encoded transaction | |
""" | |
if offset is None: | |
# Convert an address to an offset | |
if address == '': | |
raise Exception('Invalid offset and address provided') | |
else: | |
#TODO check address validity | |
offset = self.get_offset(address) | |
donglePath = self._parse_bip32_path(offset) | |
apdu = bytes.fromhex('e0040000') # https://github.com/LedgerHQ/blue-app-eth/blob/master/doc/ethapp.asc#sign-eth-transaction | |
apdu += bytes([len(donglePath) + 1 + len(rlp_encoded_tx)]) | |
apdu += bytes([len(donglePath) // 4]) | |
apdu += donglePath | |
apdu += rlp_encoded_tx | |
# Sign with dongle | |
result = self.dongle.exchange(apdu, timeout=60) | |
# Retrieve VRS from sig | |
v = result[0] | |
r = int.from_bytes(result[1:1 + 32], 'big') | |
s = int.from_bytes(result[1 + 32: 1 + 32 + 32], 'big') | |
return (v, r, s) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Requirements: pip install rlp eth-account | |
from ledger import LedgerAccount | |
import rlp | |
from eth_account.internal.transactions import serializable_unsigned_transaction_from_dict, encode_transaction | |
ledger = LedgerAccount() | |
print(ledger.list()) | |
transaction = { | |
# Note that the address must be in checksum format: | |
'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', | |
'value': 0, | |
'gas': 2000000, | |
'gasPrice': 65536, | |
'nonce': 0, | |
'chainId': 1 | |
} | |
unsigned_transaction = serializable_unsigned_transaction_from_dict(transaction) | |
(v, r, s) = ledger.sign(rlp.encode(unsigned_transaction), offset=0) | |
encoded_transaction = encode_transaction(unsigned_transaction, vrs=(v, r, s)) | |
print('0x' + encoded_transaction.hex()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment