1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 05:28:40 +00:00
trezor-firmware/python/trezorlib/eos.py
matejcik 2e578572dd add EOS support
Squashed commit of the following:

commit 060563458fbc3b4a17f4d77ba5cd62d0c265c806
Author: matejcik <ja@matejcik.cz>
Date:   Fri May 10 16:16:19 2019 +0200

    skip t1 in eos test

commit f759089fef29501467b62bf1540715132a72c4cf
Author: matejcik <ja@matejcik.cz>
Date:   Fri May 10 15:55:20 2019 +0200

    make style

commit 3ecdd5f77b331d7a6e5a46a10c79d80f214f31bd
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Thu May 9 22:05:41 2019 +0300

    Refinements in asset to to string conversion function according to code review and test cases for amounts less than 1

commit 72e44a35bada76abdd94ab866c2113a6d9d85191
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Wed May 8 00:27:45 2019 +0300

    Moved to input_flow rest of the tests, cleanup and styling

commit 92f9acbabcbef44a6912b074a309393450f0c8de
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Tue May 7 21:47:12 2019 +0300

    Fix for amounts less then 1

commit 8a0154f7432ab78e69a123202a97194d34c2a3cb
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Mon May 6 23:26:24 2019 +0300

    removed unnecessary peace of code

commit b25c15de3eb1df863760e81ca69f09094349c26e
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Mon May 6 23:16:57 2019 +0300

    Fixed validate path parameters

commit f0f6e7036a8b88d9c5c6b702a8d851e9a9bd3378
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Mon May 6 23:04:58 2019 +0300

    Fixes

commit 0c64d3814300df86d452975b2bd46fea13f512d2
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Mon May 6 22:19:51 2019 +0300

    Fixed styling

commit 41d1e77231e7da78fade9b2efa1b7d1980f0d3a8
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Mon May 6 22:13:58 2019 +0300

    Changes to core, added CURVE to path validation

commit c045b4554ee8e058dbfe35f715b003d0d85ab1d4
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Mon May 6 22:07:37 2019 +0300

    Changes according to review

commit 3f0e6cfd40e7d87dc3287bc3a0b2b9db5dea5377
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Mon Apr 29 21:37:16 2019 +0300

    Added change to make expiration date timezone agnostic

commit efdf44c326cc3f3137c447e798db5439b57c91fa
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Thu Apr 18 00:14:30 2019 +0300

    changes according to code review

commit 3b3723da8f8f536c7c370a14236ea81aac25080a
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Tue Apr 16 23:44:50 2019 +0300

    Merged python to monorepo

commit da6b0c683c29388e15c889ecea6e7f7471961a19
Author: Andriy Tkachyshyn <atkachyshyn@gmail.com>
Date:   Tue Apr 16 23:13:42 2019 +0300

    Merged core to monorepo
2019-05-10 16:23:18 +02:00

333 lines
9.3 KiB
Python

from datetime import datetime
from . import messages
from .tools import CallException, b58decode, expect, session
def name_to_number(name):
length = len(name)
value = 0
for i in range(0, 13):
c = 0
if i < length and i < 13:
c = char_to_symbol(name[i])
if i < 12:
c &= 0x1F
c <<= 64 - 5 * (i + 1)
else:
c &= 0x0F
value |= c
return value
def char_to_symbol(c):
if c >= "a" and c <= "z":
return ord(c) - ord("a") + 6
elif c >= "1" and c <= "5":
return ord(c) - ord("1") + 1
else:
return 0
def parse_asset(asset):
amount_str, symbol_str = asset.split(" ")
# "-1.0000" => ["-1", "0000"] => -10000
amount_parts = amount_str.split(".", maxsplit=1)
amount = int("".join(amount_parts))
precision = 0
if len(amount_parts) > 1:
precision = len(amount_parts[1])
# 4, "EOS" => b"\x04EOS" => little-endian uint32
symbol_bytes = bytes([precision]) + symbol_str.encode()
symbol = int.from_bytes(symbol_bytes, "little")
return messages.EosAsset(amount=amount, symbol=symbol)
def public_key_to_buffer(pub_key):
_t = 0
if pub_key[:3] == "EOS":
pub_key = pub_key[3:]
_t = 0
elif pub_key[:7] == "PUB_K1_":
pub_key = pub_key[7:]
_t = 0
elif pub_key[:7] == "PUB_R1_":
pub_key = pub_key[7:]
_t = 1
return _t, b58decode(pub_key, None)[:-4]
def parse_common(action):
authorization = []
for auth in action["authorization"]:
authorization.append(
messages.EosPermissionLevel(
actor=name_to_number(auth["actor"]),
permission=name_to_number(auth["permission"]),
)
)
return messages.EosActionCommon(
account=name_to_number(action["account"]),
name=name_to_number(action["name"]),
authorization=authorization,
)
def parse_transfer(data):
return messages.EosActionTransfer(
sender=name_to_number(data["from"]),
receiver=name_to_number(data["to"]),
memo=data["memo"],
quantity=parse_asset(data["quantity"]),
)
def parse_vote_producer(data):
producers = []
for producer in data["producers"]:
producers.append(name_to_number(producer))
return messages.EosActionVoteProducer(
voter=name_to_number(data["account"]),
proxy=name_to_number(data["proxy"]),
producers=producers,
)
def parse_buy_ram(data):
return messages.EosActionBuyRam(
payer=name_to_number(data["payer"]),
receiver=name_to_number(data["receiver"]),
quantity=parse_asset(data["quant"]),
)
def parse_buy_rambytes(data):
return messages.EosActionBuyRamBytes(
payer=name_to_number(data["payer"]),
receiver=name_to_number(data["receiver"]),
bytes=int(data["bytes"]),
)
def parse_sell_ram(data):
return messages.EosActionSellRam(
account=name_to_number(data["account"]), bytes=int(data["bytes"])
)
def parse_delegate(data):
return messages.EosActionDelegate(
sender=name_to_number(data["sender"]),
receiver=name_to_number(data["receiver"]),
net_quantity=parse_asset(data["stake_net_quantity"]),
cpu_quantity=parse_asset(data["stake_cpu_quantity"]),
transfer=bool(data["transfer"]),
)
def parse_undelegate(data):
return messages.EosActionUndelegate(
sender=name_to_number(data["sender"]),
receiver=name_to_number(data["receiver"]),
net_quantity=parse_asset(data["unstake_net_quantity"]),
cpu_quantity=parse_asset(data["unstake_cpu_quantity"]),
)
def parse_refund(data):
return messages.EosActionRefund(owner=name_to_number(data["owner"]))
def parse_updateauth(data):
auth = parse_authorization(data["auth"])
return messages.EosActionUpdateAuth(
account=name_to_number(data["account"]),
permission=name_to_number(data["permission"]),
parent=name_to_number(data["parent"]),
auth=auth,
)
def parse_deleteauth(data):
return messages.EosActionDeleteAuth(
account=name_to_number(data["account"]),
permission=name_to_number(data["permission"]),
)
def parse_linkauth(data):
return messages.EosActionLinkAuth(
account=name_to_number(data["account"]),
code=name_to_number(data["code"]),
type=name_to_number(data["type"]),
requirement=name_to_number(data["requirement"]),
)
def parse_unlinkauth(data):
return messages.EosActionUnlinkAuth(
account=name_to_number(data["account"]),
code=name_to_number(data["code"]),
type=name_to_number(data["type"]),
)
def parse_authorization(data):
keys = []
for key in data["keys"]:
_t, _k = public_key_to_buffer(key["key"])
keys.append(
messages.EosAuthorizationKey(type=_t, key=_k, weight=int(key["weight"]))
)
accounts = []
for account in data["accounts"]:
accounts.append(
messages.EosAuthorizationAccount(
account=messages.EosPermissionLevel(
actor=name_to_number(account["permission"]["actor"]),
permission=name_to_number(account["permission"]["permission"]),
),
weight=int(account["weight"]),
)
)
waits = []
for wait in data["waits"]:
waits.append(
messages.EosAuthorizationWait(
wait_sec=int(wait["wait_sec"]), weight=int(wait["weight"])
)
)
return messages.EosAuthorization(
threshold=int(data["threshold"]), keys=keys, accounts=accounts, waits=waits
)
def parse_new_account(data):
owner = parse_authorization(data["owner"])
active = parse_authorization(data["active"])
return messages.EosActionNewAccount(
creator=name_to_number(data["creator"]),
name=name_to_number(data["name"]),
owner=owner,
active=active,
)
def parse_unknown(data):
data_bytes = bytes.fromhex(data)
return messages.EosActionUnknown(data_size=len(data_bytes), data_chunk=data_bytes)
def parse_action(action):
tx_action = messages.EosTxActionAck()
data = action["data"]
tx_action.common = parse_common(action)
if action["account"] == "eosio":
if action["name"] == "voteproducer":
tx_action.vote_producer = parse_vote_producer(data)
elif action["name"] == "buyram":
tx_action.buy_ram = parse_buy_ram(data)
elif action["name"] == "buyrambytes":
tx_action.buy_ram_bytes = parse_buy_rambytes(data)
elif action["name"] == "sellram":
tx_action.sell_ram = parse_sell_ram(data)
elif action["name"] == "delegatebw":
tx_action.delegate = parse_delegate(data)
elif action["name"] == "undelegatebw":
tx_action.undelegate = parse_undelegate(data)
elif action["name"] == "refund":
tx_action.refund = parse_refund(data)
elif action["name"] == "updateauth":
tx_action.update_auth = parse_updateauth(data)
elif action["name"] == "deleteauth":
tx_action.delete_auth = parse_deleteauth(data)
elif action["name"] == "linkauth":
tx_action.link_auth = parse_linkauth(data)
elif action["name"] == "unlinkauth":
tx_action.unlink_auth = parse_unlinkauth(data)
elif action["name"] == "newaccount":
tx_action.new_account = parse_new_account(data)
elif action["name"] == "transfer":
tx_action.transfer = parse_transfer(data)
else:
tx_action.unknown = parse_unknown(data)
return tx_action
def parse_transaction_json(transaction):
header = messages.EosTxHeader()
header.expiration = int(
(
datetime.strptime(transaction["expiration"], "%Y-%m-%dT%H:%M:%S")
- datetime(1970, 1, 1)
).total_seconds()
)
header.ref_block_num = int(transaction["ref_block_num"])
header.ref_block_prefix = int(transaction["ref_block_prefix"])
header.max_net_usage_words = int(transaction["net_usage_words"])
header.max_cpu_usage_ms = int(transaction["max_cpu_usage_ms"])
header.delay_sec = int(transaction["delay_sec"])
actions = [parse_action(a) for a in transaction["actions"]]
return header, actions
# ====== Client functions ====== #
@expect(messages.EosPublicKey)
def get_public_key(client, n, show_display=False, multisig=None):
response = client.call(
messages.EosGetPublicKey(address_n=n, show_display=show_display)
)
return response
@session
def sign_tx(client, address, transaction, chain_id):
header, actions = parse_transaction_json(transaction)
msg = messages.EosSignTx()
msg.address_n = address
msg.chain_id = bytes.fromhex(chain_id)
msg.header = header
msg.num_actions = len(actions)
response = client.call(msg)
try:
while isinstance(response, messages.EosTxActionRequest):
response = client.call(actions.pop(0))
except IndexError:
# pop from empty list
raise CallException(
"Eos.UnexpectedEndOfOperations",
"Reached end of operations without a signature.",
) from None
if not isinstance(response, messages.EosSignedTx):
raise CallException(messages.FailureType.UnexpectedMessage, response)
return response