Skip to content

Commit

Permalink
Merge pull request #78 from LedgerHQ/y333/support_nbgl_icon_for_nano
Browse files Browse the repository at this point in the history
Icon shall be transformed into NBGL uncompressed buffer for Nano S+ a…
  • Loading branch information
yogh333 authored Sep 6, 2024
2 parents 6bd85b6 + 6b47bb4 commit e14223b
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 25 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.1] - 2024-08-23

### Fixed

- For Nano S+ and X, generate NBGL compliant buffer (uncompressed) for app icon (from api_level > 5)

## [0.5.0] - 2024-05-22

### Add
Expand Down
56 changes: 35 additions & 21 deletions ledgerwallet/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def is_power2(n):
return n != 0 and ((n & (n - 1)) == 0)


def _image_to_compressed_buffer_nbgl(im: Image) -> bytes:
def _image_to_buffer_nbgl(im: Image, compress: bool) -> bytes:
im = im.convert("L")
nb_colors = len(im.getcolors())

Expand Down Expand Up @@ -72,23 +72,26 @@ def _image_to_compressed_buffer_nbgl(im: Image) -> bytes:
if current_bit > 0:
image_data.append(current_byte & 0xFF)

# Compress buffer into a gzip file
output_buffer = []
# cut into chunks of 2048 bytes max of uncompressed data
# (because decompression needs the full buffer)
full_uncompressed_size = len(image_data)
i = 0
while full_uncompressed_size > 0:
chunk_size = min(2048, full_uncompressed_size)
tmp = bytes(image_data[i : i + chunk_size])
compressed_buffer = gzip.compress(tmp, mtime=0)
output_buffer += [
len(compressed_buffer) & 0xFF,
(len(compressed_buffer) >> 8) & 0xFF,
]
output_buffer += compressed_buffer
full_uncompressed_size -= chunk_size
i += chunk_size
if not compress:
output_buffer = image_data
else:
# Compress buffer into a gzip file
output_buffer = []
# cut into chunks of 2048 bytes max of uncompressed data
# (because decompression needs the full buffer)
full_uncompressed_size = len(image_data)
i = 0
while full_uncompressed_size > 0:
chunk_size = min(2048, full_uncompressed_size)
tmp = bytes(image_data[i : i + chunk_size])
compressed_buffer = gzip.compress(tmp, mtime=0)
output_buffer += [
len(compressed_buffer) & 0xFF,
(len(compressed_buffer) >> 8) & 0xFF,
]
output_buffer += compressed_buffer
full_uncompressed_size -= chunk_size
i += chunk_size

# Add metadata
BPP_FORMATS = {1: 0, 2: 1, 4: 2}
Expand All @@ -99,7 +102,7 @@ def _image_to_compressed_buffer_nbgl(im: Image) -> bytes:
height & 0xFF,
height >> 8,
(BPP_FORMATS[bpp] << 4)
| 1, # 1 is gzip compression type. We only support gzip.
| (1 if compress else 0), # 0 is no compression, 1 is gzip compression type
len(output_buffer) & 0xFF,
(len(output_buffer) >> 8) & 0xFF,
(len(output_buffer) >> 16) & 0xFF,
Expand Down Expand Up @@ -186,7 +189,7 @@ def _image_to_packed_buffer_bagl(im: Image) -> bytes:
return header + bytes(image_data)


def icon_from_file(image_file: str, device: str) -> bytes:
def icon_from_file(image_file: str, device: str, api_level: Optional[int]) -> bytes:
im = Image.open(image_file)
im.load()

Expand All @@ -196,7 +199,18 @@ def icon_from_file(image_file: str, device: str) -> bytes:
DeviceNames.LEDGER_STAX.value,
DeviceNames.LEDGER_FLEX.value,
]:
image_data = _image_to_compressed_buffer_nbgl(im)
image_data = _image_to_buffer_nbgl(im, True)

elif (
get_device_name(int(device, 16))
in [
DeviceNames.LEDGER_NANO_SP.value,
DeviceNames.LEDGER_NANO_X.value,
]
and api_level is not None
and api_level > 5
):
image_data = _image_to_buffer_nbgl(im, False)
else:
image_data = _image_to_packed_buffer_bagl(im)

Expand Down
6 changes: 5 additions & 1 deletion ledgerwallet/manifest_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ def serialize_parameters(self, device: str) -> bytes:
elif entry == "version":
parameters.append({"type_": "BOLOS_TAG_APPVERSION", "value": value})
elif entry == "icon":
api_level = self.get_api_level(device)
parameters.append(
{"type_": "BOLOS_TAG_ICON", "value": icon_from_file(value, device)}
{
"type_": "BOLOS_TAG_ICON",
"value": icon_from_file(value, device, api_level),
}
)
elif entry == "derivationPath":
derivation_paths = self.serialize_derivation_path(value)
Expand Down
5 changes: 4 additions & 1 deletion ledgerwallet/manifest_toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@ def serialize_parameters(self, device: str) -> bytes:
elif entry == device:
for device_entry, device_value in self.dic[entry].items():
if device_entry == "icon":
api_level = self.get_api_level(device)
parameters.append(
{
"type_": "BOLOS_TAG_ICON",
"value": icon_from_file(device_value, device),
"value": icon_from_file(
device_value, device, api_level
),
}
)
elif device_entry == "derivationPath":
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_manifest_ManifestJSON.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_serialize_parameters(self):
# fmt: on
with patch(
"ledgerwallet.manifest_json.icon_from_file",
lambda x, y: b"\x01\x02\x03\x04",
lambda x, y, z: b"\x01\x02\x03\x04",
):
result_json = self.json_manifest.serialize_parameters("1234")
self.assertEqual(result_json, expected)
2 changes: 1 addition & 1 deletion tests/unit/test_manifest_ManifestTOML.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def test_serialize_parameters(self):
# fmt: on
with patch(
"ledgerwallet.manifest_toml.icon_from_file",
lambda x, y: b"\x01\x02\x03\x04",
lambda x, y, z: b"\x01\x02\x03\x04",
):
result_toml = self.toml_manifest.serialize_parameters("1234")
self.assertEqual(result_toml, expected)

0 comments on commit e14223b

Please sign in to comment.