diff --git a/core/src/apps/cardano/helpers/hash_builder_collection.py b/core/src/apps/cardano/helpers/hash_builder_collection.py index 33998cd72..c846ed488 100644 --- a/core/src/apps/cardano/helpers/hash_builder_collection.py +++ b/core/src/apps/cardano/helpers/hash_builder_collection.py @@ -120,3 +120,20 @@ class HashBuilderDict(HashBuilderCollection, Generic[K, V]): def _header_bytes(self) -> bytes: return cbor.create_map_header(self.size) + + +class HashBuilderEmbeddedCBOR(HashBuilderCollection): + """ + Accepts data chunks and serializes them as embedded CBOR. + Note that self.remaining holds the remaining data size in bytes (not chunks). + Child HashBuilders are not allowed. + """ + + def add(self, chunk: bytes) -> None: + assert self.hash_fn is not None + assert self.remaining >= len(chunk) + self.remaining -= len(chunk) + self.hash_fn.update(chunk) + + def _header_bytes(self) -> bytes: + return cbor.create_embedded_cbor_bytes_header(self.size) diff --git a/core/src/apps/common/cbor.py b/core/src/apps/common/cbor.py index 8de734f7e..dd90a4ba1 100644 --- a/core/src/apps/common/cbor.py +++ b/core/src/apps/common/cbor.py @@ -320,6 +320,14 @@ def create_map_header(size: int) -> bytes: return _header(_CBOR_MAP, size) +def create_embedded_cbor_bytes_header(size: int) -> bytes: + """ + Bytes wrapped in Tag 24 (embedded CBOR). + https://datatracker.ietf.org/doc/html/rfc7049#section-2.4.4.1 + """ + return _header(_CBOR_TAG, _CBOR_RAW_TAG) + _header(_CBOR_BYTE_STRING, size) + + def precedes(prev: bytes, curr: bytes) -> bool: """ Returns True if `prev` is smaller than `curr` with regards to diff --git a/core/tests/test_apps.common.cbor.py b/core/tests/test_apps.common.cbor.py index d0d67e690..ad57b0582 100644 --- a/core/tests/test_apps.common.cbor.py +++ b/core/tests/test_apps.common.cbor.py @@ -8,6 +8,7 @@ from apps.common.cbor import ( Tagged, create_array_header, create_map_header, + create_embedded_cbor_bytes_header, decode, encode, encode_chunked, @@ -48,6 +49,22 @@ class TestCardanoCbor(unittest.TestCase): with self.assertRaises(NotImplementedError): create_map_header(2 ** 64) + def test_create_embedded_cbor_bytes_header(self): + test_vectors = [ + (0, 'd81840'), + (23, 'd81857'), + ((2 ** 8) - 1, 'd81858ff'), + ((2 ** 16) - 1, 'd81859ffff'), + ((2 ** 32) - 1, 'd8185affffffff'), + ((2 ** 64) - 1, 'd8185bffffffffffffffff'), + ] + for val, header_hex in test_vectors: + header = unhexlify(header_hex) + self.assertEqual(create_embedded_cbor_bytes_header(val), header) + + with self.assertRaises(NotImplementedError): + create_embedded_cbor_bytes_header(2 ** 64) + def test_cbor_encoding(self): test_vectors = [ # unsigned integers