mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-04-25 11:39:02 +00:00
core/cbor: move CBOR from cardano to common
- add support for text strings and boolean values - add support negative integers and decoding maps - fix decoding of short strings - encode maps canonically - add unit tests for decoding - sort maps lexicographically by encoded key
This commit is contained in:
parent
751715dc15
commit
31506d81e9
@ -1,8 +1,7 @@
|
|||||||
from trezor import log
|
from trezor import log
|
||||||
from trezor.crypto import base58, crc, hashlib
|
from trezor.crypto import base58, crc, hashlib
|
||||||
|
|
||||||
from apps.cardano import cbor
|
from apps.common import HARDENED, cbor
|
||||||
from apps.common import HARDENED
|
|
||||||
from apps.common.seed import remove_ed25519_prefix
|
from apps.common.seed import remove_ed25519_prefix
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@ from trezor.messages.CardanoSignedTx import CardanoSignedTx
|
|||||||
from trezor.messages.CardanoTxRequest import CardanoTxRequest
|
from trezor.messages.CardanoTxRequest import CardanoTxRequest
|
||||||
from trezor.messages.MessageType import CardanoTxAck
|
from trezor.messages.MessageType import CardanoTxAck
|
||||||
|
|
||||||
from apps.cardano import CURVE, cbor, seed
|
from apps.cardano import CURVE, seed
|
||||||
from apps.cardano.address import (
|
from apps.cardano.address import (
|
||||||
derive_address_and_node,
|
derive_address_and_node,
|
||||||
is_safe_output_address,
|
is_safe_output_address,
|
||||||
validate_full_path,
|
validate_full_path,
|
||||||
)
|
)
|
||||||
from apps.cardano.layout import confirm_sending, confirm_transaction, progress
|
from apps.cardano.layout import confirm_sending, confirm_transaction, progress
|
||||||
|
from apps.common import cbor
|
||||||
from apps.common.paths import validate_path
|
from apps.common.paths import validate_path
|
||||||
from apps.common.seed import remove_ed25519_prefix
|
from apps.common.seed import remove_ed25519_prefix
|
||||||
from apps.homescreen.homescreen import display_homescreen
|
from apps.homescreen.homescreen import display_homescreen
|
||||||
|
@ -12,7 +12,9 @@ _CBOR_TYPE_MASK = const(0xE0)
|
|||||||
_CBOR_INFO_BITS = const(0x1F)
|
_CBOR_INFO_BITS = const(0x1F)
|
||||||
|
|
||||||
_CBOR_UNSIGNED_INT = const(0b000 << 5)
|
_CBOR_UNSIGNED_INT = const(0b000 << 5)
|
||||||
|
_CBOR_NEGATIVE_INT = const(0b001 << 5)
|
||||||
_CBOR_BYTE_STRING = const(0b010 << 5)
|
_CBOR_BYTE_STRING = const(0b010 << 5)
|
||||||
|
_CBOR_TEXT_STRING = const(0b011 << 5)
|
||||||
_CBOR_ARRAY = const(0b100 << 5)
|
_CBOR_ARRAY = const(0b100 << 5)
|
||||||
_CBOR_MAP = const(0b101 << 5)
|
_CBOR_MAP = const(0b101 << 5)
|
||||||
_CBOR_TAG = const(0b110 << 5)
|
_CBOR_TAG = const(0b110 << 5)
|
||||||
@ -24,6 +26,8 @@ _CBOR_UINT32_FOLLOWS = const(0x1A)
|
|||||||
_CBOR_UINT64_FOLLOWS = const(0x1B)
|
_CBOR_UINT64_FOLLOWS = const(0x1B)
|
||||||
_CBOR_VAR_FOLLOWS = const(0x1F)
|
_CBOR_VAR_FOLLOWS = const(0x1F)
|
||||||
|
|
||||||
|
_CBOR_FALSE = const(0x14)
|
||||||
|
_CBOR_TRUE = const(0x15)
|
||||||
_CBOR_BREAK = const(0x1F)
|
_CBOR_BREAK = const(0x1F)
|
||||||
_CBOR_RAW_TAG = const(0x18)
|
_CBOR_RAW_TAG = const(0x18)
|
||||||
|
|
||||||
@ -45,13 +49,20 @@ def _header(typ, l: int):
|
|||||||
|
|
||||||
def _cbor_encode(value):
|
def _cbor_encode(value):
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
yield _header(_CBOR_UNSIGNED_INT, value)
|
if value >= 0:
|
||||||
|
yield _header(_CBOR_UNSIGNED_INT, value)
|
||||||
|
else:
|
||||||
|
yield _header(_CBOR_NEGATIVE_INT, -1 - value)
|
||||||
elif isinstance(value, bytes):
|
elif isinstance(value, bytes):
|
||||||
yield _header(_CBOR_BYTE_STRING, len(value))
|
yield _header(_CBOR_BYTE_STRING, len(value))
|
||||||
yield value
|
yield value
|
||||||
elif isinstance(value, bytearray):
|
elif isinstance(value, bytearray):
|
||||||
yield _header(_CBOR_BYTE_STRING, len(value))
|
yield _header(_CBOR_BYTE_STRING, len(value))
|
||||||
yield bytes(value)
|
yield bytes(value)
|
||||||
|
elif isinstance(value, str):
|
||||||
|
encoded_value = value.encode()
|
||||||
|
yield _header(_CBOR_TEXT_STRING, len(encoded_value))
|
||||||
|
yield encoded_value
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
# definite-length valued list
|
# definite-length valued list
|
||||||
yield _header(_CBOR_ARRAY, len(value))
|
yield _header(_CBOR_ARRAY, len(value))
|
||||||
@ -59,8 +70,9 @@ def _cbor_encode(value):
|
|||||||
yield from _cbor_encode(x)
|
yield from _cbor_encode(x)
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
yield _header(_CBOR_MAP, len(value))
|
yield _header(_CBOR_MAP, len(value))
|
||||||
for k, v in value.items():
|
sorted_map = sorted((encode(k), v) for k, v in value.items())
|
||||||
yield from _cbor_encode(k)
|
for k, v in sorted_map:
|
||||||
|
yield k
|
||||||
yield from _cbor_encode(v)
|
yield from _cbor_encode(v)
|
||||||
elif isinstance(value, Tagged):
|
elif isinstance(value, Tagged):
|
||||||
yield _header(_CBOR_TAG, value.tag)
|
yield _header(_CBOR_TAG, value.tag)
|
||||||
@ -70,6 +82,11 @@ def _cbor_encode(value):
|
|||||||
for x in value.array:
|
for x in value.array:
|
||||||
yield from _cbor_encode(x)
|
yield from _cbor_encode(x)
|
||||||
yield bytes([_CBOR_PRIMITIVE + 31])
|
yield bytes([_CBOR_PRIMITIVE + 31])
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
if value:
|
||||||
|
yield bytes([_CBOR_PRIMITIVE + _CBOR_TRUE])
|
||||||
|
else:
|
||||||
|
yield bytes([_CBOR_PRIMITIVE + _CBOR_FALSE])
|
||||||
elif isinstance(value, Raw):
|
elif isinstance(value, Raw):
|
||||||
yield value.value
|
yield value.value
|
||||||
else:
|
else:
|
||||||
@ -79,7 +96,9 @@ def _cbor_encode(value):
|
|||||||
|
|
||||||
|
|
||||||
def _read_length(cbor, aux):
|
def _read_length(cbor, aux):
|
||||||
if aux == _CBOR_UINT8_FOLLOWS:
|
if aux < _CBOR_UINT8_FOLLOWS:
|
||||||
|
return (aux, cbor)
|
||||||
|
elif aux == _CBOR_UINT8_FOLLOWS:
|
||||||
return (cbor[0], cbor[1:])
|
return (cbor[0], cbor[1:])
|
||||||
elif aux == _CBOR_UINT16_FOLLOWS:
|
elif aux == _CBOR_UINT16_FOLLOWS:
|
||||||
res = cbor[1]
|
res = cbor[1]
|
||||||
@ -111,14 +130,16 @@ def _cbor_decode(cbor):
|
|||||||
fb_type = fb & _CBOR_TYPE_MASK
|
fb_type = fb & _CBOR_TYPE_MASK
|
||||||
fb_aux = fb & _CBOR_INFO_BITS
|
fb_aux = fb & _CBOR_INFO_BITS
|
||||||
if fb_type == _CBOR_UNSIGNED_INT:
|
if fb_type == _CBOR_UNSIGNED_INT:
|
||||||
if fb_aux < 0x18:
|
return _read_length(cbor[1:], fb_aux)
|
||||||
return (fb_aux, cbor[1:])
|
elif fb_type == _CBOR_NEGATIVE_INT:
|
||||||
else:
|
val, data = _read_length(cbor[1:], fb_aux)
|
||||||
val, data = _read_length(cbor[1:], fb_aux)
|
return (-1 - val, data)
|
||||||
return (int(val), data)
|
|
||||||
elif fb_type == _CBOR_BYTE_STRING:
|
elif fb_type == _CBOR_BYTE_STRING:
|
||||||
ln, data = _read_length(cbor[1:], fb_aux)
|
ln, data = _read_length(cbor[1:], fb_aux)
|
||||||
return (data[0:ln], data[ln:])
|
return (data[0:ln], data[ln:])
|
||||||
|
elif fb_type == _CBOR_TEXT_STRING:
|
||||||
|
ln, data = _read_length(cbor[1:], fb_aux)
|
||||||
|
return (data[0:ln].decode(), data[ln:])
|
||||||
elif fb_type == _CBOR_ARRAY:
|
elif fb_type == _CBOR_ARRAY:
|
||||||
if fb_aux == _CBOR_VAR_FOLLOWS:
|
if fb_aux == _CBOR_VAR_FOLLOWS:
|
||||||
res = []
|
res = []
|
||||||
@ -130,25 +151,49 @@ def _cbor_decode(cbor):
|
|||||||
res.append(item)
|
res.append(item)
|
||||||
return (res, data)
|
return (res, data)
|
||||||
else:
|
else:
|
||||||
if fb_aux < _CBOR_UINT8_FOLLOWS:
|
ln, data = _read_length(cbor[1:], fb_aux)
|
||||||
ln = fb_aux
|
|
||||||
data = cbor[1:]
|
|
||||||
else:
|
|
||||||
ln, data = _read_length(cbor[1:], fb_aux)
|
|
||||||
res = []
|
res = []
|
||||||
for i in range(ln):
|
for i in range(ln):
|
||||||
item, data = _cbor_decode(data)
|
item, data = _cbor_decode(data)
|
||||||
res.append(item)
|
res.append(item)
|
||||||
return (res, data)
|
return (res, data)
|
||||||
elif fb_type == _CBOR_MAP:
|
elif fb_type == _CBOR_MAP:
|
||||||
return ({}, cbor[1:])
|
res = {}
|
||||||
|
if fb_aux == _CBOR_VAR_FOLLOWS:
|
||||||
|
data = cbor[1:]
|
||||||
|
while True:
|
||||||
|
key, data = _cbor_decode(data)
|
||||||
|
if key in res:
|
||||||
|
raise ValueError
|
||||||
|
if key == _CBOR_PRIMITIVE + _CBOR_BREAK:
|
||||||
|
break
|
||||||
|
value, data = _cbor_decode(data)
|
||||||
|
res[key] = value
|
||||||
|
else:
|
||||||
|
ln, data = _read_length(cbor[1:], fb_aux)
|
||||||
|
for i in range(ln):
|
||||||
|
key, data = _cbor_decode(data)
|
||||||
|
if key in res:
|
||||||
|
raise ValueError
|
||||||
|
value, data = _cbor_decode(data)
|
||||||
|
res[key] = value
|
||||||
|
return res, data
|
||||||
elif fb_type == _CBOR_TAG:
|
elif fb_type == _CBOR_TAG:
|
||||||
if cbor[1] == _CBOR_RAW_TAG: # only tag 24 (0x18) is supported
|
val, data = _read_length(cbor[1:], fb_aux)
|
||||||
return _cbor_decode(cbor[2:])
|
item, data = _cbor_decode(data)
|
||||||
|
if val == _CBOR_RAW_TAG: # only tag 24 (0x18) is supported
|
||||||
|
return item, data
|
||||||
|
else:
|
||||||
|
return Tagged(val, item), data
|
||||||
|
elif fb_type == _CBOR_PRIMITIVE:
|
||||||
|
if fb_aux == _CBOR_FALSE:
|
||||||
|
return (False, cbor[1:])
|
||||||
|
elif fb_aux == _CBOR_TRUE:
|
||||||
|
return (True, cbor[1:])
|
||||||
|
elif fb_aux == _CBOR_BREAK:
|
||||||
|
return (cbor[0], cbor[1:])
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
elif fb_type == _CBOR_PRIMITIVE: # only break code is supported
|
|
||||||
return (cbor[0], cbor[1:])
|
|
||||||
else:
|
else:
|
||||||
if __debug__:
|
if __debug__:
|
||||||
log.debug(__name__, "not implemented (decode): %s", cbor[0])
|
log.debug(__name__, "not implemented (decode): %s", cbor[0])
|
||||||
@ -160,6 +205,9 @@ class Tagged:
|
|||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.tag == other.tag and self.value == other.value
|
||||||
|
|
||||||
|
|
||||||
class Raw:
|
class Raw:
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
@ -171,6 +219,12 @@ class IndefiniteLengthArray:
|
|||||||
ensure(isinstance(array, list))
|
ensure(isinstance(array, list))
|
||||||
self.array = array
|
self.array = array
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, IndefiniteLengthArray):
|
||||||
|
return self.array == other.array
|
||||||
|
else:
|
||||||
|
return self.array == other
|
||||||
|
|
||||||
|
|
||||||
def encode(value):
|
def encode(value):
|
||||||
return b"".join(_cbor_encode(value))
|
return b"".join(_cbor_encode(value))
|
@ -1,16 +1,17 @@
|
|||||||
from common import *
|
from common import *
|
||||||
|
|
||||||
from apps.cardano.cbor import (
|
from apps.common.cbor import (
|
||||||
Tagged,
|
Tagged,
|
||||||
IndefiniteLengthArray,
|
IndefiniteLengthArray,
|
||||||
encode
|
decode,
|
||||||
|
encode,
|
||||||
)
|
)
|
||||||
from ubinascii import unhexlify
|
from ubinascii import unhexlify
|
||||||
|
|
||||||
class TestCardanoCbor(unittest.TestCase):
|
class TestCardanoCbor(unittest.TestCase):
|
||||||
def test_cbor_encoding(self):
|
def test_cbor_encoding(self):
|
||||||
test_vectors = [
|
test_vectors = [
|
||||||
# integers
|
# unsigned integers
|
||||||
(0, '00'),
|
(0, '00'),
|
||||||
(1, '01'),
|
(1, '01'),
|
||||||
(10, '0a'),
|
(10, '0a'),
|
||||||
@ -22,10 +23,26 @@ class TestCardanoCbor(unittest.TestCase):
|
|||||||
(1000000, '1a000f4240'),
|
(1000000, '1a000f4240'),
|
||||||
(1000000000000, '1b000000e8d4a51000'),
|
(1000000000000, '1b000000e8d4a51000'),
|
||||||
|
|
||||||
|
# negative integers
|
||||||
|
(-1, '20'),
|
||||||
|
(-10, '29'),
|
||||||
|
(-24, '37'),
|
||||||
|
(-25, '3818'),
|
||||||
|
(-26, '3819'),
|
||||||
|
(-100, '3863'),
|
||||||
|
(-1000, '3903E7'),
|
||||||
|
(-1000000, '3A000F423F'),
|
||||||
|
(-1000000000000, '3B000000E8D4A50FFF'),
|
||||||
|
|
||||||
# binary strings
|
# binary strings
|
||||||
(b'', '40'),
|
(b'', '40'),
|
||||||
(unhexlify('01020304'), '4401020304'),
|
(unhexlify('01020304'), '4401020304'),
|
||||||
|
|
||||||
|
# text strings
|
||||||
|
('', '60'),
|
||||||
|
('Fun', '6346756e'),
|
||||||
|
(u'P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f', '786550c599c3adc5a165726ec49b20c5be6c75c5a56f75c48d6bc3bd206bc5afc58820c3ba70c49b6c20c48fc3a162656c736bc3a920c3b36479207ac3a16b65c5996ec3bd2075c48d65c5882062c49bc5bec3ad20706f64c3a96c207ac3b36e7920c3ba6cc5af'),
|
||||||
|
|
||||||
# tags
|
# tags
|
||||||
(Tagged(1, 1363896240), 'c11a514b67b0'),
|
(Tagged(1, 1363896240), 'c11a514b67b0'),
|
||||||
(Tagged(23, unhexlify('01020304')), 'd74401020304'),
|
(Tagged(23, unhexlify('01020304')), 'd74401020304'),
|
||||||
@ -38,19 +55,21 @@ class TestCardanoCbor(unittest.TestCase):
|
|||||||
|
|
||||||
# maps
|
# maps
|
||||||
({}, 'a0'),
|
({}, 'a0'),
|
||||||
|
({1: 2, 3: 4}, 'a201020304'),
|
||||||
# Note: normal python dict doesn't have a fixed item ordering
|
|
||||||
({1: 2, 3: 4}, 'a203040102'),
|
|
||||||
|
|
||||||
# indefinite
|
# indefinite
|
||||||
(IndefiniteLengthArray([]), '9fff'),
|
(IndefiniteLengthArray([]), '9fff'),
|
||||||
(IndefiniteLengthArray([1, [2, 3], [4, 5]]), '9f01820203820405ff'),
|
(IndefiniteLengthArray([1, [2, 3], [4, 5]]), '9f01820203820405ff'),
|
||||||
(IndefiniteLengthArray([1, [2, 3], IndefiniteLengthArray([4, 5])]),
|
(IndefiniteLengthArray([1, [2, 3], IndefiniteLengthArray([4, 5])]),
|
||||||
'9f018202039f0405ffff'),
|
'9f018202039f0405ffff'),
|
||||||
|
|
||||||
|
# boolean
|
||||||
|
(True, 'f5'),
|
||||||
|
(False, 'f4'),
|
||||||
]
|
]
|
||||||
for val, expected in test_vectors:
|
for val, encoded in test_vectors:
|
||||||
encoded = encode(val)
|
self.assertEqual(unhexlify(encoded), encode(val))
|
||||||
self.assertEqual(unhexlify(expected), encoded)
|
self.assertEqual(val, decode(unhexlify(encoded)))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user