stellar: accounts are addresses

pull/25/head
Tomas Susanka 6 years ago
parent bcf77bd347
commit 7f767eec46

@ -1,21 +1,36 @@
from trezor.crypto import base32
from trezor.wire import ProcessError
import ustruct
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):
"""Returns the base32-encoded version of public key bytes (G...)"""
address = bytearray()
address.append(6 << 3) # version -> 'G'
address.extend(pubkey)
address.extend(ustruct.pack("<H", _crc16_checksum(address))) # checksum
address.extend(_crc16_checksum(bytes(address))) # checksum
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
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:
crc ^= polynomial
return crc & 0xffff
return ustruct.pack("<H", crc & 0xffff)

@ -1,16 +1,15 @@
from apps.common.confirm import require_confirm, require_hold_to_confirm
from apps.stellar import consts
from apps.stellar import helpers
from trezor import ui
from trezor.messages import ButtonRequestType
from trezor.ui.text import Text
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,
ui.NORMAL, 'Initialize singing with',
ui.MONO, *split(format_address(pubkey)),
ui.MONO, *split(address),
icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
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
def format_address(pubkey: bytes) -> str:
return helpers.address_from_public_key(pubkey)
# todo merge with nem
def split(text):
return utils.chunks(text, 17)

@ -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 trezor.messages import ButtonRequestType
from trezor.ui.text import Text
@ -20,7 +20,7 @@ from ubinascii import hexlify
async def confirm_source_account(ctx, source_account: bytes):
content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, 'Source account:',
ui.MONO, *split(format_address(source_account)),
ui.MONO, *split(source_account),
icon_color=ui.GREEN)
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,
ui.BOLD, text,
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)
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,
ui.BOLD, 'Account Merge',
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)
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,
ui.BOLD, 'Create Account',
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)
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,
ui.BOLD, 'Path Pay %s' % format_amount(op.destination_amount, ticker=False),
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)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
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,
ui.BOLD, 'Pay %s' % format_amount(op.amount, ticker=False),
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)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
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:
content = Text('Confirm operation', ui.ICON_CONFIRM,
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)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
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:
content = Text('Confirm operation', ui.ICON_CONFIRM,
ui.BOLD, text % 'acc',
ui.MONO, *split(format_address(op.signer_key)),
ui.MONO, *split(op.signer_key),
icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)
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
content = Text('Confirm issuer', ui.ICON_CONFIRM,
ui.BOLD, '%s issuer:' % asset.code,
ui.MONO, *split(format_address(asset.issuer)),
ui.MONO, *split(asset.issuer),
icon_color=ui.GREEN)
await require_confirm(ctx, content, ButtonRequestType.ConfirmOutput)

@ -135,7 +135,7 @@ def serialize_set_options_op(w, msg: StellarSetOptionsOp):
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:
writers.write_bool(w, False)
return

@ -1,8 +1,10 @@
from apps.common import seed
from apps.stellar import writers
from apps.stellar.operations import operation
from apps.stellar import helpers
from apps.stellar import layout
from apps.stellar import consts
from trezor.wire import ProcessError
from trezor.messages.StellarSignTx import StellarSignTx
from trezor.messages.StellarTxOpRequest import StellarTxOpRequest
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, consts.TX_TYPE)
writers.write_pubkey(w, pubkey)
if msg.source_account != pubkey:
raise ValueError('Stellar: source account does not match address_n')
address = helpers.address_from_public_key(pubkey)
writers.write_pubkey(w, address)
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_uint64(w, msg.sequence_number)
# 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):

@ -1,4 +1,5 @@
import ustruct
from .helpers import public_key_from_address
def write_uint32(w, n: int):
@ -29,7 +30,8 @@ def write_bool(w, val: True):
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)
write_uint32(w, 0)
pubkey = public_key_from_address(address)
write_bytes(w, bytearray(pubkey))

@ -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()
Loading…
Cancel
Save