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

stellar: accounts are addresses

This commit is contained in:
Tomas Susanka 2018-06-14 14:05:41 +02:00
parent bcf77bd347
commit 7f767eec46
7 changed files with 80 additions and 27 deletions

View File

@ -1,21 +1,36 @@
from trezor.crypto import base32 from trezor.crypto import base32
from trezor.wire import ProcessError
import ustruct import ustruct
STELLAR_CURVE = 'ed25519' STELLAR_CURVE = 'ed25519'
def public_key_from_address(address: str) -> bytes:
"""Extracts public key from an address
Stellar address is in format:
<1-byte version> <32-bytes ed25519 public key> <2-bytes CRC-16 checksum>
"""
b = base32.decode(address)
_crc16_checksum_verify(b[:-2], b[-2:])
return b[1:-2]
def address_from_public_key(pubkey: bytes): def address_from_public_key(pubkey: bytes):
"""Returns the base32-encoded version of public key bytes (G...)""" """Returns the base32-encoded version of public key bytes (G...)"""
address = bytearray() address = bytearray()
address.append(6 << 3) # version -> 'G' address.append(6 << 3) # version -> 'G'
address.extend(pubkey) address.extend(pubkey)
address.extend(ustruct.pack("<H", _crc16_checksum(address))) # checksum address.extend(_crc16_checksum(bytes(address))) # checksum
return base32.encode(address) return base32.encode(address)
def _crc16_checksum(data: bytearray): def _crc16_checksum_verify(data: bytes, checksum: bytes):
if _crc16_checksum(data) != checksum:
raise ProcessError('Invalid address checksum')
def _crc16_checksum(data: bytes) -> bytes:
"""Returns the CRC-16 checksum of bytearray bytes """Returns the CRC-16 checksum of bytearray bytes
Ported from Java implementation at: http://introcs.cs.princeton.edu/java/61data/CRC16CCITT.java.html Ported from Java implementation at: http://introcs.cs.princeton.edu/java/61data/CRC16CCITT.java.html
@ -33,4 +48,4 @@ def _crc16_checksum(data: bytearray):
if c15 ^ bit: if c15 ^ bit:
crc ^= polynomial crc ^= polynomial
return crc & 0xffff return ustruct.pack("<H", crc & 0xffff)

View File

@ -1,16 +1,15 @@
from apps.common.confirm import require_confirm, require_hold_to_confirm from apps.common.confirm import require_confirm, require_hold_to_confirm
from apps.stellar import consts from apps.stellar import consts
from apps.stellar import helpers
from trezor import ui from trezor import ui
from trezor.messages import ButtonRequestType from trezor.messages import ButtonRequestType
from trezor.ui.text import Text from trezor.ui.text import Text
from trezor import utils from trezor import utils
async def require_confirm_init(ctx, pubkey: bytes, network_passphrase: str): async def require_confirm_init(ctx, address: str, network_passphrase: str):
content = Text('Confirm Stellar', ui.ICON_SEND, content = Text('Confirm Stellar', ui.ICON_SEND,
ui.NORMAL, 'Initialize singing with', ui.NORMAL, 'Initialize singing with',
ui.MONO, *split(format_address(pubkey)), ui.MONO, *split(address),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
network = get_network_warning(network_passphrase) network = get_network_warning(network_passphrase)
@ -62,10 +61,6 @@ def format_amount(amount: int, ticker=True) -> str:
return utils.format_amount(amount, consts.AMOUNT_DIVISIBILITY) + t return utils.format_amount(amount, consts.AMOUNT_DIVISIBILITY) + t
def format_address(pubkey: bytes) -> str:
return helpers.address_from_public_key(pubkey)
# todo merge with nem # todo merge with nem
def split(text): def split(text):
return utils.chunks(text, 17) return utils.chunks(text, 17)

View File

@ -1,4 +1,4 @@
from apps.stellar.layout import split, format_address, format_amount, ui, trim_to_rows, require_confirm from apps.stellar.layout import split, format_amount, ui, trim_to_rows, require_confirm
from apps.stellar import consts from apps.stellar import consts
from trezor.messages import ButtonRequestType from trezor.messages import ButtonRequestType
from trezor.ui.text import Text from trezor.ui.text import Text
@ -20,7 +20,7 @@ from ubinascii import hexlify
async def confirm_source_account(ctx, source_account: bytes): async def confirm_source_account(ctx, source_account: bytes):
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, 'Source account:', ui.BOLD, 'Source account:',
ui.MONO, *split(format_address(source_account)), ui.MONO, *split(source_account),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
@ -33,7 +33,7 @@ async def confirm_allow_trust_op(ctx, op: StellarAllowTrustOp):
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, text, ui.BOLD, text,
ui.NORMAL, 'of %s by:' % op.asset_code, ui.NORMAL, 'of %s by:' % op.asset_code,
ui.MONO, *split(trim_to_rows(format_address(op.trusted_account), 3)), ui.MONO, *split(trim_to_rows(op.trusted_account, 3)),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
@ -43,7 +43,7 @@ async def confirm_account_merge_op(ctx, op: StellarAccountMergeOp):
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, 'Account Merge', ui.BOLD, 'Account Merge',
ui.NORMAL, 'All XLM will be sent to:', ui.NORMAL, 'All XLM will be sent to:',
ui.MONO, *split(trim_to_rows(format_address(op.destination_account), 3)), ui.MONO, *split(trim_to_rows(op.destination_account, 3)),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
@ -75,7 +75,7 @@ async def confirm_create_account_op(ctx, op: StellarCreateAccountOp):
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, 'Create Account', ui.BOLD, 'Create Account',
ui.NORMAL, 'with %s' % format_amount(op.starting_balance), ui.NORMAL, 'with %s' % format_amount(op.starting_balance),
ui.MONO, *split(trim_to_rows(format_address(op.new_account), 3)), ui.MONO, *split(trim_to_rows(op.new_account, 3)),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
@ -137,7 +137,7 @@ async def confirm_path_payment_op(ctx, op: StellarPathPaymentOp):
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, 'Path Pay %s' % format_amount(op.destination_amount, ticker=False), ui.BOLD, 'Path Pay %s' % format_amount(op.destination_amount, ticker=False),
ui.BOLD, '%s to:' % format_asset_code(op.destination_asset), ui.BOLD, '%s to:' % format_asset_code(op.destination_asset),
ui.MONO, *split(trim_to_rows(format_address(op.destination_account), 3)), ui.MONO, *split(trim_to_rows(op.destination_account, 3)),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
await confirm_asset_issuer(ctx, op.destination_asset) await confirm_asset_issuer(ctx, op.destination_asset)
@ -157,7 +157,7 @@ async def confirm_payment_op(ctx, op: StellarPaymentOp):
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, 'Pay %s' % format_amount(op.amount, ticker=False), ui.BOLD, 'Pay %s' % format_amount(op.amount, ticker=False),
ui.BOLD, '%s to:' % format_asset_code(op.asset), ui.BOLD, '%s to:' % format_asset_code(op.asset),
ui.MONO, *split(trim_to_rows(format_address(op.destination_account), 3)), ui.MONO, *split(trim_to_rows(op.destination_account, 3)),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
await confirm_asset_issuer(ctx, op.asset) await confirm_asset_issuer(ctx, op.asset)
@ -167,7 +167,7 @@ async def confirm_set_options_op(ctx, op: StellarSetOptionsOp):
if op.inflation_destination_account: if op.inflation_destination_account:
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, 'Set Inflation Destination', ui.BOLD, 'Set Inflation Destination',
ui.MONO, *split(format_address(op.inflation_destination_account)), ui.MONO, *split(op.inflation_destination_account),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
if op.clear_flags: if op.clear_flags:
@ -205,7 +205,7 @@ async def confirm_set_options_op(ctx, op: StellarSetOptionsOp):
if op.signer_type == consts.SIGN_TYPE_ACCOUNT: if op.signer_type == consts.SIGN_TYPE_ACCOUNT:
content = Text('Confirm operation', ui.ICON_CONFIRM, content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, text % 'acc', ui.BOLD, text % 'acc',
ui.MONO, *split(format_address(op.signer_key)), ui.MONO, *split(op.signer_key),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
elif op.signer_type in (consts.SIGN_TYPE_PRE_AUTH, consts.SIGN_TYPE_HASH): elif op.signer_type in (consts.SIGN_TYPE_PRE_AUTH, consts.SIGN_TYPE_HASH):
@ -259,6 +259,6 @@ async def confirm_asset_issuer(ctx, asset: StellarAssetType):
return return
content = Text('Confirm issuer', ui.ICON_CONFIRM, content = Text('Confirm issuer', ui.ICON_CONFIRM,
ui.BOLD, '%s issuer:' % asset.code, ui.BOLD, '%s issuer:' % asset.code,
ui.MONO, *split(format_address(asset.issuer)), ui.MONO, *split(asset.issuer),
icon_color=ui.GREEN) icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput) await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)

View File

@ -135,7 +135,7 @@ def serialize_set_options_op(w, msg: StellarSetOptionsOp):
writers.write_uint32(w, msg.signer_weight) writers.write_uint32(w, msg.signer_weight)
def serialize_account(w, source_account: bytes): def serialize_account(w, source_account: str):
if source_account is None: if source_account is None:
writers.write_bool(w, False) writers.write_bool(w, False)
return return

View File

@ -1,8 +1,10 @@
from apps.common import seed from apps.common import seed
from apps.stellar import writers from apps.stellar import writers
from apps.stellar.operations import operation from apps.stellar.operations import operation
from apps.stellar import helpers
from apps.stellar import layout from apps.stellar import layout
from apps.stellar import consts from apps.stellar import consts
from trezor.wire import ProcessError
from trezor.messages.StellarSignTx import StellarSignTx from trezor.messages.StellarSignTx import StellarSignTx
from trezor.messages.StellarTxOpRequest import StellarTxOpRequest from trezor.messages.StellarTxOpRequest import StellarTxOpRequest
from trezor.messages.StellarSignedTx import StellarSignedTx from trezor.messages.StellarSignedTx import StellarSignedTx
@ -45,14 +47,15 @@ async def _init(ctx, w: bytearray, pubkey: bytes, msg: StellarSignTx):
writers.write_bytes(w, network_passphrase_hash) writers.write_bytes(w, network_passphrase_hash)
writers.write_bytes(w, consts.TX_TYPE) writers.write_bytes(w, consts.TX_TYPE)
writers.write_pubkey(w, pubkey) address = helpers.address_from_public_key(pubkey)
if msg.source_account != pubkey: writers.write_pubkey(w, address)
raise ValueError('Stellar: source account does not match address_n') if helpers.public_key_from_address(msg.source_account) != pubkey:
raise ProcessError('Stellar: source account does not match address_n')
writers.write_uint32(w, msg.fee) writers.write_uint32(w, msg.fee)
writers.write_uint64(w, msg.sequence_number) writers.write_uint64(w, msg.sequence_number)
# confirm init # confirm init
await layout.require_confirm_init(ctx, pubkey, msg.network_passphrase) await layout.require_confirm_init(ctx, address, msg.network_passphrase)
def _timebounds(w: bytearray, start: int, end: int): def _timebounds(w: bytearray, start: int, end: int):

View File

@ -1,4 +1,5 @@
import ustruct import ustruct
from .helpers import public_key_from_address
def write_uint32(w, n: int): def write_uint32(w, n: int):
@ -29,7 +30,8 @@ def write_bool(w, val: True):
write_uint32(w, 0) write_uint32(w, 0)
def write_pubkey(w, pubkey: bytes): def write_pubkey(w, address: str):
# first 4 bytes of an address are the type, there's only one type (0) # first 4 bytes of an address are the type, there's only one type (0)
write_uint32(w, 0) write_uint32(w, 0)
pubkey = public_key_from_address(address)
write_bytes(w, bytearray(pubkey)) write_bytes(w, bytearray(pubkey))

View File

@ -0,0 +1,38 @@
from common import *
from apps.stellar.helpers import address_from_public_key, public_key_from_address
from trezor.wire import ProcessError
class TestStellarAddressToPubkey(unittest.TestCase):
def test_address_to_pubkey(self):
self.assertEqual(public_key_from_address('GBOVKZBEM2YYLOCDCUXJ4IMRKHN4LCJAE7WEAEA2KF562XFAGDBOB64V'),
unhexlify('5d55642466b185b843152e9e219151dbc5892027ec40101a517bed5ca030c2e0'))
self.assertEqual(public_key_from_address('GCN2K2HG53AWX2SP5UHRPMJUUHLJF2XBTGSXROTPWRGAYJCDDP63J2U6'),
unhexlify('9ba568e6eec16bea4fed0f17b134a1d692eae199a578ba6fb44c0c24431bfdb4'))
def test_pubkey_to_address(self):
addr = address_from_public_key(unhexlify('5d55642466b185b843152e9e219151dbc5892027ec40101a517bed5ca030c2e0'))
self.assertEqual(addr, 'GBOVKZBEM2YYLOCDCUXJ4IMRKHN4LCJAE7WEAEA2KF562XFAGDBOB64V')
addr = address_from_public_key(unhexlify('9ba568e6eec16bea4fed0f17b134a1d692eae199a578ba6fb44c0c24431bfdb4'))
self.assertEqual(addr, 'GCN2K2HG53AWX2SP5UHRPMJUUHLJF2XBTGSXROTPWRGAYJCDDP63J2U6')
def test_both(self):
pubkey = unhexlify('dfcc77d08588601702e02de2dc603f5c5281bea23baa894ae3b3b4778e5bbe40')
self.assertEqual(public_key_from_address(address_from_public_key(pubkey)), pubkey)
pubkey = unhexlify('53214e6155469c32fb882b1b1d94930d5445a78202867b7ddc6a33ad42ff4464')
self.assertEqual(public_key_from_address(address_from_public_key(pubkey)), pubkey)
pubkey = unhexlify('5ed4690134e5ef79b290ea1e7a4b8f3b6b3bcf287463c18bfe36baa030e7efbd')
self.assertEqual(public_key_from_address(address_from_public_key(pubkey)), pubkey)
def test_invalid_address(self):
with self.assertRaises(ProcessError):
public_key_from_address('GCN2K2HG53AWX2SP5UHRPMJUUHLJF2XBTGSXROTPWRGAYJCDDP63J2AA') # invalid checksum
if __name__ == '__main__':
unittest.main()