This repository has been archived by the owner on May 28, 2019. It is now read-only.
Tezos integration #292
Merged
Merged
Tezos integration #292
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
84be7cd
tezos: added tezos cryptocurrency
f2d5a36
tezos: display_address -> show
2a15331
tezos: removed duplicated code
c7b7df7
tezos: supporting only ed25519 curve
7c60790
tezos: run_xfail set
dc6e405
tezos: fixed nitpicks
1709902
tezos: added Readme
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Tezos | ||
|
||
Tezos documentation can be found [here](http://tezos.gitlab.io). | ||
|
||
## Operations | ||
|
||
Tezos allows users to use multiple curves for private key derivation, but we support | ||
only `ed25519` (because it is the most used one) where addresses are prefixed with `tz1`, | ||
public keys with `edpk` and signatures with `edsig`. Other curves might be added later on. | ||
|
||
Trezor supports basic Tezos user operations - reveal, transaction, origination, delegation. | ||
When the account creates first operation in lifetime, reveal has to be bundled | ||
with this operation to reveal account's public key. | ||
|
||
#### Operations Explorer | ||
|
||
[TzScan](http://tzscan.io) | ||
|
||
## Tests | ||
|
||
Unit tests are located in the `tests` directory, device tests are in the python-trezor repository. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from trezor import wire | ||
from trezor.messages import MessageType | ||
|
||
|
||
def boot(): | ||
wire.add(MessageType.TezosGetAddress, __name__, "get_address") | ||
wire.add(MessageType.TezosSignTx, __name__, "sign_tx") | ||
wire.add(MessageType.TezosGetPublicKey, __name__, "get_public_key") |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from trezor.crypto import hashlib | ||
from trezor.messages.TezosAddress import TezosAddress | ||
|
||
from apps.common import seed | ||
from apps.common.layout import show_address, show_qr | ||
from apps.tezos.helpers import ( | ||
TEZOS_CURVE, | ||
TEZOS_ED25519_ADDRESS_PREFIX, | ||
base58_encode_check, | ||
) | ||
|
||
|
||
async def get_address(ctx, msg): | ||
address_n = msg.address_n or () | ||
node = await seed.derive_node(ctx, address_n, TEZOS_CURVE) | ||
|
||
pk = seed.remove_ed25519_prefix(node.public_key()) | ||
pkh = hashlib.blake2b(pk, outlen=20).digest() | ||
address = base58_encode_check(pkh, prefix=TEZOS_ED25519_ADDRESS_PREFIX) | ||
|
||
if msg.show_display: | ||
while True: | ||
if await show_address(ctx, address): | ||
break | ||
if await show_qr(ctx, address): | ||
break | ||
|
||
return TezosAddress(address=address) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from trezor import ui | ||
from trezor.messages import ButtonRequestType | ||
from trezor.messages.TezosPublicKey import TezosPublicKey | ||
from trezor.ui.text import Text | ||
from trezor.utils import chunks | ||
|
||
from apps.common import seed | ||
from apps.common.confirm import require_confirm | ||
from apps.tezos.helpers import TEZOS_CURVE, TEZOS_PUBLICKEY_PREFIX, base58_encode_check | ||
|
||
|
||
async def get_public_key(ctx, msg): | ||
address_n = msg.address_n or () | ||
node = await seed.derive_node(ctx, address_n, TEZOS_CURVE) | ||
|
||
pk = seed.remove_ed25519_prefix(node.public_key()) | ||
pk_prefixed = base58_encode_check(pk, prefix=TEZOS_PUBLICKEY_PREFIX) | ||
|
||
if msg.show_display: | ||
await _show_tezos_pubkey(ctx, pk_prefixed) | ||
|
||
return TezosPublicKey(public_key=pk_prefixed) | ||
|
||
|
||
async def _show_tezos_pubkey(ctx, pubkey): | ||
lines = chunks(pubkey, 18) | ||
text = Text("Confirm public key", ui.ICON_RECEIVE, icon_color=ui.GREEN) | ||
text.mono(*lines) | ||
return await require_confirm(ctx, text, code=ButtonRequestType.PublicKey) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from micropython import const | ||
|
||
from trezor.crypto import base58 | ||
|
||
TEZOS_CURVE = "ed25519" | ||
TEZOS_AMOUNT_DIVISIBILITY = const(6) | ||
TEZOS_ED25519_ADDRESS_PREFIX = "tz1" | ||
TEZOS_ORIGINATED_ADDRESS_PREFIX = "KT1" | ||
TEZOS_PUBLICKEY_PREFIX = "edpk" | ||
TEZOS_SIGNATURE_PREFIX = "edsig" | ||
TEZOS_PREFIX_BYTES = { | ||
# addresses | ||
"tz1": [6, 161, 159], | ||
"tz2": [6, 161, 161], | ||
"tz3": [6, 161, 164], | ||
"KT1": [2, 90, 121], | ||
# public keys | ||
"edpk": [13, 15, 37, 217], | ||
# signatures | ||
"edsig": [9, 245, 205, 134, 18], | ||
# operation hash | ||
"o": [5, 116], | ||
} | ||
|
||
|
||
def base58_encode_check(payload, prefix=None): | ||
result = payload | ||
if prefix is not None: | ||
result = bytes(TEZOS_PREFIX_BYTES[prefix]) + payload | ||
return base58.encode_check(result) | ||
|
||
|
||
def base58_decode_check(enc, prefix=None): | ||
decoded = base58.decode_check(enc) | ||
if prefix is not None: | ||
decoded = decoded[len(TEZOS_PREFIX_BYTES[prefix]) :] | ||
return decoded |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
from trezor import ui | ||
from trezor.messages import ButtonRequestType | ||
from trezor.ui.text import Text | ||
from trezor.utils import chunks, format_amount | ||
|
||
from apps.common.confirm import * | ||
from apps.tezos.helpers import TEZOS_AMOUNT_DIVISIBILITY | ||
|
||
|
||
async def require_confirm_tx(ctx, to, value): | ||
text = Text("Confirm sending", ui.ICON_SEND, icon_color=ui.GREEN) | ||
text.bold(format_tezos_amount(value)) | ||
text.normal("to") | ||
text.mono(*split_address(to)) | ||
return await require_confirm(ctx, text, ButtonRequestType.SignTx) | ||
|
||
|
||
async def require_confirm_fee(ctx, value, fee): | ||
text = Text("Confirm transaction", ui.ICON_SEND, icon_color=ui.GREEN) | ||
text.normal("Amount:") | ||
text.bold(format_tezos_amount(value)) | ||
text.normal("Fee:") | ||
text.bold(format_tezos_amount(fee)) | ||
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx) | ||
|
||
|
||
async def require_confirm_origination(ctx, address): | ||
text = Text("Confirm origination", ui.ICON_SEND, icon_color=ui.ORANGE) | ||
text.normal("Address:") | ||
text.mono(*split_address(address)) | ||
return await require_confirm(ctx, text, ButtonRequestType.SignTx) | ||
|
||
|
||
async def require_confirm_origination_fee(ctx, balance, fee): | ||
text = Text("Confirm origination", ui.ICON_SEND, icon_color=ui.ORANGE) | ||
text.normal("Balance:") | ||
text.bold(format_tezos_amount(balance)) | ||
text.normal("Fee:") | ||
text.bold(format_tezos_amount(fee)) | ||
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx) | ||
|
||
|
||
async def require_confirm_delegation_baker(ctx, baker): | ||
text = Text("Confirm delegation", ui.ICON_SEND, icon_color=ui.BLUE) | ||
text.normal("Baker address:") | ||
text.mono(*split_address(baker)) | ||
return await require_confirm(ctx, text, ButtonRequestType.SignTx) | ||
|
||
|
||
async def require_confirm_set_delegate(ctx, fee): | ||
text = Text("Confirm delegation", ui.ICON_SEND, icon_color=ui.BLUE) | ||
text.normal("Fee:") | ||
text.bold(format_tezos_amount(fee)) | ||
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx) | ||
|
||
|
||
async def require_confirm_register_delegate(ctx, address, fee): | ||
text = Text("Register delegate", ui.ICON_SEND, icon_color=ui.BLUE) | ||
text.bold("Fee: " + format_tezos_amount(fee)) | ||
text.normal("Address:") | ||
text.mono(*split_address(address)) | ||
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx) | ||
|
||
|
||
def split_address(address): | ||
return chunks(address, 18) | ||
|
||
|
||
def format_tezos_amount(value): | ||
formatted_value = format_amount(value, TEZOS_AMOUNT_DIVISIBILITY) | ||
return formatted_value + " XTZ" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
from trezor import wire | ||
from trezor.crypto import hashlib | ||
from trezor.crypto.curve import ed25519 | ||
from trezor.messages import TezosContractType | ||
from trezor.messages.TezosSignedTx import TezosSignedTx | ||
|
||
from apps.common import seed | ||
from apps.common.writers import write_bytes, write_uint8 | ||
from apps.tezos.helpers import ( | ||
TEZOS_CURVE, | ||
TEZOS_ORIGINATED_ADDRESS_PREFIX, | ||
TEZOS_SIGNATURE_PREFIX, | ||
base58_encode_check, | ||
) | ||
from apps.tezos.layout import * | ||
|
||
|
||
async def sign_tx(ctx, msg): | ||
address_n = msg.address_n or () | ||
node = await seed.derive_node(ctx, address_n, TEZOS_CURVE) | ||
|
||
if msg.transaction is not None: | ||
to = _get_address_from_contract(msg.transaction.destination) | ||
await require_confirm_tx(ctx, to, msg.transaction.amount) | ||
await require_confirm_fee(ctx, msg.transaction.amount, msg.transaction.fee) | ||
|
||
elif msg.origination is not None: | ||
source = _get_address_from_contract(msg.origination.source) | ||
await require_confirm_origination(ctx, source) | ||
await require_confirm_origination_fee( | ||
ctx, msg.origination.balance, msg.origination.fee | ||
) | ||
|
||
elif msg.delegation is not None: | ||
source = _get_address_from_contract(msg.delegation.source) | ||
|
||
delegate = None | ||
if msg.delegation.delegate is not None: | ||
delegate = _get_address_by_tag(msg.delegation.delegate) | ||
|
||
if delegate is not None and source != delegate: | ||
await require_confirm_delegation_baker(ctx, delegate) | ||
await require_confirm_set_delegate(ctx, msg.delegation.fee) | ||
# if account registers itself as a delegate | ||
else: | ||
await require_confirm_register_delegate(ctx, source, msg.delegation.fee) | ||
|
||
else: | ||
raise wire.DataError("Invalid operation") | ||
|
||
w = bytearray() | ||
_get_operation_bytes(w, msg) | ||
|
||
opbytes = bytes(w) | ||
|
||
# watermark 0x03 is prefix for transactions, delegations, originations, reveals... | ||
watermark = bytes([3]) | ||
wm_opbytes = watermark + opbytes | ||
wm_opbytes_hash = hashlib.blake2b(wm_opbytes, outlen=32).digest() | ||
|
||
signature = ed25519.sign(node.private_key(), wm_opbytes_hash) | ||
|
||
sig_op_contents = opbytes + signature | ||
sig_op_contents_hash = hashlib.blake2b(sig_op_contents, outlen=32).digest() | ||
ophash = base58_encode_check(sig_op_contents_hash, prefix="o") | ||
|
||
sig_prefixed = base58_encode_check(signature, prefix=TEZOS_SIGNATURE_PREFIX) | ||
|
||
return TezosSignedTx( | ||
signature=sig_prefixed, sig_op_contents=sig_op_contents, operation_hash=ophash | ||
) | ||
|
||
|
||
def _get_address_by_tag(address_hash): | ||
prefixes = ["tz1", "tz2", "tz3"] | ||
tag = int(address_hash[0]) | ||
|
||
if 0 <= tag < len(prefixes): | ||
return base58_encode_check(address_hash[1:], prefix=prefixes[tag]) | ||
raise wire.DataError("Invalid tag in address hash") | ||
|
||
|
||
def _get_address_from_contract(address): | ||
if address.tag == TezosContractType.Implicit: | ||
return _get_address_by_tag(address.hash) | ||
|
||
elif address.tag == TezosContractType.Originated: | ||
return base58_encode_check( | ||
address.hash[:-1], prefix=TEZOS_ORIGINATED_ADDRESS_PREFIX | ||
) | ||
|
||
raise wire.DataError("Invalid contract type") | ||
|
||
|
||
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 | ||
if msg.reveal is not None: | ||
_encode_common(w, msg.reveal, "reveal") | ||
write_bytes(w, msg.reveal.public_key) | ||
|
||
# transaction operation | ||
if msg.transaction is not None: | ||
_encode_common(w, msg.transaction, "transaction") | ||
_encode_zarith(w, msg.transaction.amount) | ||
_encode_contract_id(w, msg.transaction.destination) | ||
_encode_data_with_bool_prefix(w, msg.transaction.parameters) | ||
# origination operation | ||
elif msg.origination is not None: | ||
_encode_common(w, msg.origination, "origination") | ||
write_bytes(w, msg.origination.manager_pubkey) | ||
_encode_zarith(w, msg.origination.balance) | ||
_encode_bool(w, msg.origination.spendable) | ||
_encode_bool(w, msg.origination.delegatable) | ||
_encode_data_with_bool_prefix(w, msg.origination.delegate) | ||
_encode_data_with_bool_prefix(w, msg.origination.script) | ||
# delegation operation | ||
elif msg.delegation is not None: | ||
_encode_common(w, msg.delegation, "delegation") | ||
_encode_data_with_bool_prefix(w, msg.delegation.delegate) | ||
|
||
|
||
def _encode_common(w: bytearray, operation, str_operation): | ||
operation_tags = {"reveal": 7, "transaction": 8, "origination": 9, "delegation": 10} | ||
write_uint8(w, operation_tags[str_operation]) | ||
_encode_contract_id(w, operation.source) | ||
_encode_zarith(w, operation.fee) | ||
_encode_zarith(w, operation.counter) | ||
_encode_zarith(w, operation.gas_limit) | ||
_encode_zarith(w, operation.storage_limit) | ||
|
||
|
||
def _encode_contract_id(w: bytearray, contract_id): | ||
write_uint8(w, contract_id.tag) | ||
write_bytes(w, contract_id.hash) | ||
|
||
|
||
def _encode_bool(w: bytearray, boolean): | ||
if boolean: | ||
write_uint8(w, 255) | ||
else: | ||
write_uint8(w, 0) | ||
|
||
|
||
def _encode_data_with_bool_prefix(w: bytearray, data): | ||
if data: | ||
_encode_bool(w, True) | ||
write_bytes(w, data) | ||
else: | ||
_encode_bool(w, False) | ||
|
||
|
||
def _encode_zarith(w: bytearray, num): | ||
while True: | ||
byte = num & 127 | ||
num = num >> 7 | ||
|
||
if num == 0: | ||
write_uint8(w, byte) | ||
break | ||
|
||
write_uint8(w, 128 | byte) |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use common.layout.split_address. It has size=17, but that doesn't matter that much or will be refactored together later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I use 17, the address in transaction won't fit on display.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, keep it.