1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-16 03:18:09 +00:00

Add support for tezos voting operations (proposal and ballot)

This commit is contained in:
Adrian Nagy 2019-03-15 19:39:16 +01:00
parent 67907b6c4c
commit a0d89c0785
7 changed files with 214 additions and 3 deletions

View File

@ -22,6 +22,8 @@ TEZOS_PREFIX_BYTES = {
"edsig": [9, 245, 205, 134, 18],
# operation hash
"o": [5, 116],
# protocola hash
"P": [2, 170],
}

View File

@ -1,5 +1,9 @@
from trezor import ui
from micropython import const
from trezor import ui, wire
from trezor.messages import ButtonRequestType
from trezor.ui.confirm import CANCELLED, CONFIRMED, ConfirmDialog
from trezor.ui.scroll import Scrollpage, animate_swipe, paginate
from trezor.ui.text import Text
from trezor.utils import chunks, format_amount
@ -66,6 +70,51 @@ def split_address(address):
return chunks(address, 18)
def split_proposal(proposal):
return chunks(proposal, 17)
def format_tezos_amount(value):
formatted_value = format_amount(value, TEZOS_AMOUNT_DIVISIBILITY)
return formatted_value + " XTZ"
async def require_confirm_proposal(ctx, proposals):
text = Text("Submit proposal", ui.ICON_SEND, icon_color=ui.PURPLE)
text.bold("Proposal:")
text.mono(*split_proposal(proposals[0]))
await require_confirm(ctx, text, ButtonRequestType.SignTx)
async def require_confirm_ballot(ctx, proposal, ballot):
text = Text("Submit ballot", ui.ICON_SEND, icon_color=ui.PURPLE)
text.bold("Ballot: {}".format(ballot))
text.bold("Proposal:")
text.mono(*split_proposal(proposal[0]))
await require_confirm(ctx, text, ButtonRequestType.SignTx)
# use, when there are more then one proposals in one operation
async def show_proposals(ctx, proposals):
first_page = const(0)
pages = proposals
paginator = paginate(show_proposal_page, len(pages), first_page, pages)
return await ctx.wait(paginator)
@ui.layout
async def show_proposal_page(page: int, page_count: int, pages: list):
text = Text("Submit proposals", ui.ICON_SEND, icon_color=ui.PURPLE)
text.bold("Proposal {}: ".format(page + 1))
text.mono(*split_proposal(pages[page]))
content = Scrollpage(text, page, page_count)
if page + 1 >= page_count:
confirm = await ConfirmDialog(content)
if confirm == CANCELLED:
raise wire.ActionCancelled("Cancelled")
else:
content.render()
await animate_swipe()

View File

@ -1,3 +1,5 @@
from micropython import const
from trezor import wire
from trezor.crypto import hashlib
from trezor.crypto.curve import ed25519
@ -5,8 +7,19 @@ from trezor.messages import TezosContractType
from trezor.messages.TezosSignedTx import TezosSignedTx
from apps.common import paths
from apps.common.writers import write_bytes, write_uint8
# from apps.common.writers import write_bytes, write_uint8
from apps.tezos import helpers, layout
from apps.tezos.writers import (
write_bool,
write_bytes,
write_uint8,
write_uint16,
write_uint32,
write_uint64,
)
PROPOSAL_LENGTH = const(32)
async def sign_tx(ctx, msg, keychain):
@ -50,6 +63,20 @@ async def sign_tx(ctx, msg, keychain):
ctx, source, msg.delegation.fee
)
elif msg.proposal is not None:
proposed_protocols = _get_protocol_hash_from_msg(msg.proposal.proposals)
# byte count larger than PROPOSAL_LENGTH indicates more than 1 proposal, use paginated screen
if msg.proposal.bytes_in_next_field > PROPOSAL_LENGTH:
await layout.show_proposals(ctx, proposed_protocols)
else:
await layout.require_confirm_proposal(ctx, proposed_protocols)
elif msg.ballot is not None:
proposed_protocol = _get_protocol_hash_from_msg(msg.ballot.proposal)
submitted_ballot = _get_ballot(msg.ballot.ballot)
await layout.require_confirm_ballot(ctx, proposed_protocol, submitted_ballot)
else:
raise wire.DataError("Invalid operation")
@ -99,11 +126,34 @@ def _get_address_from_contract(address):
raise wire.DataError("Invalid contract type")
def _get_protocol_hash_from_msg(proposals):
# split the proposals
proposal_list = list(
[
proposals[i : i + PROPOSAL_LENGTH]
for i in range(0, len(proposals), PROPOSAL_LENGTH)
]
)
return [
helpers.base58_encode_check(proposal, prefix="P") for proposal in proposal_list
]
def _get_ballot(encoded_ballot):
encoded_ballot = int(encoded_ballot[0])
if encoded_ballot == 0:
return "yay"
elif encoded_ballot == 1:
return "nay"
elif encoded_ballot == 2:
return "pass"
def _get_operation_bytes(w: bytearray, msg):
write_bytes(w, msg.branch)
# when the account sends first operation in lifetime,
# we need to reveal its publickey
# we need to reveal its public key
if msg.reveal is not None:
_encode_common(w, msg.reveal, "reveal")
write_bytes(w, msg.reveal.public_key)
@ -127,6 +177,10 @@ def _get_operation_bytes(w: bytearray, msg):
elif msg.delegation is not None:
_encode_common(w, msg.delegation, "delegation")
_encode_data_with_bool_prefix(w, msg.delegation.delegate)
elif msg.proposal is not None:
_encode_proposal(w, msg.proposal)
elif msg.ballot is not None:
_encode_ballot(w, msg.ballot)
def _encode_common(w: bytearray, operation, str_operation):
@ -169,3 +223,23 @@ def _encode_zarith(w: bytearray, num):
break
write_uint8(w, 128 | byte)
def _encode_proposal(w: bytearray, proposal):
proposal_tag = 5
write_uint8(w, proposal_tag)
write_bytes(w, proposal.source)
write_uint32(w, proposal.period)
write_uint32(w, proposal.bytes_in_next_field)
write_bytes(w, proposal.proposals)
def _encode_ballot(w: bytearray, ballot):
ballot_tag = 6
write_uint8(w, ballot_tag)
write_bytes(w, ballot.source)
write_uint32(w, ballot.period)
write_bytes(w, ballot.proposal)
write_bytes(w, ballot.ballot)

24
src/apps/tezos/writers.py Normal file
View File

@ -0,0 +1,24 @@
from apps.common.writers import (
write_bytes,
write_uint8,
write_uint32_be,
write_uint64_be,
)
write_uint8 = write_uint8
write_uint32 = write_uint32_be
write_uint64 = write_uint64_be
write_bytes = write_bytes
def write_bool(w: bytearray, boolean: bool):
if boolean:
write_uint8(w, 255)
else:
write_uint8(w, 0)
# write uint16 in be
def write_uint16(w: bytearray, n: int):
w.append((n >> 8) & 0xFF)
w.append(n & 0xFF)

View File

@ -0,0 +1,27 @@
# Automatically generated by pb2py
# fmt: off
import protobuf as p
class TezosBallotOp(p.MessageType):
def __init__(
self,
source: bytes = None,
period: int = None,
proposal: bytes = None,
ballot: bytes = None,
) -> None:
self.source = source
self.period = period
self.proposal = proposal
self.ballot = ballot
@classmethod
def get_fields(cls):
return {
1: ('source', p.BytesType, 0),
2: ('period', p.UVarintType, 0),
3: ('proposal', p.BytesType, 0),
4: ('ballot', p.BytesType, 0),
}

View File

@ -0,0 +1,27 @@
# Automatically generated by pb2py
# fmt: off
import protobuf as p
class TezosProposalOp(p.MessageType):
def __init__(
self,
source: bytes = None,
period: int = None,
bytes_in_next_field: int = None,
proposals: bytes = None,
) -> None:
self.source = source
self.period = period
self.bytes_in_next_field = bytes_in_next_field
self.proposals = proposals
@classmethod
def get_fields(cls):
return {
1: ('source', p.BytesType, 0),
2: ('period', p.UVarintType, 0),
3: ('bytes_in_next_field', p.UVarintType, 0),
4: ('proposals', p.BytesType, 0),
}

View File

@ -2,8 +2,10 @@
# fmt: off
import protobuf as p
from .TezosBallotOp import TezosBallotOp
from .TezosDelegationOp import TezosDelegationOp
from .TezosOriginationOp import TezosOriginationOp
from .TezosProposalOp import TezosProposalOp
from .TezosRevealOp import TezosRevealOp
from .TezosTransactionOp import TezosTransactionOp
@ -25,6 +27,8 @@ class TezosSignTx(p.MessageType):
transaction: TezosTransactionOp = None,
origination: TezosOriginationOp = None,
delegation: TezosDelegationOp = None,
proposal: TezosProposalOp = None,
ballot: TezosBallotOp = None,
) -> None:
self.address_n = address_n if address_n is not None else []
self.branch = branch
@ -32,6 +36,8 @@ class TezosSignTx(p.MessageType):
self.transaction = transaction
self.origination = origination
self.delegation = delegation
self.proposal = proposal
self.ballot = ballot
@classmethod
def get_fields(cls):
@ -42,4 +48,6 @@ class TezosSignTx(p.MessageType):
4: ('transaction', TezosTransactionOp, 0),
5: ('origination', TezosOriginationOp, 0),
6: ('delegation', TezosDelegationOp, 0),
7: ('proposal', TezosProposalOp, 0),
8: ('ballot', TezosBallotOp, 0),
}