mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-27 00:28:10 +00:00
stellar: accounts are addresses
This commit is contained in:
parent
bcf77bd347
commit
7f767eec46
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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))
|
||||||
|
38
tests/test_apps.stellar.address_to_pubkey.py
Normal file
38
tests/test_apps.stellar.address_to_pubkey.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user