Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

B2CA-1566: Dynamic network management #635

Merged
merged 7 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 80 additions & 24 deletions client/src/ledger_app_clients/ethereum/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .keychain import sign_data, Key
from .tlv import format_tlv

from hashlib import sha256
from web3 import Web3


Expand Down Expand Up @@ -43,21 +44,25 @@ class TrustedNameSource(IntEnum):
DNS = 0x05


class TrustedNameTag(IntEnum):
class FieldTag(IntEnum):
STRUCT_TYPE = 0x01
STRUCT_VERSION = 0x02
NOT_VALID_AFTER = 0x10
CHALLENGE = 0x12
SIGNER_KEY_ID = 0x13
SIGNER_ALGO = 0x14
SIGNATURE = 0x15
NAME = 0x20
DER_SIGNATURE = 0x15
TRUSTED_NAME = 0x20
COIN_TYPE = 0x21
ADDRESS = 0x22
CHAIN_ID = 0x23
NAME_TYPE = 0x70
NAME_SOURCE = 0x71
NFT_ID = 0x72
TICKER = 0x24
BLOCKCHAIN_FAMILY = 0x51
NETWORK_NAME = 0x52
NETWORK_ICON_HASH = 0x53
TRUSTED_NAME_TYPE = 0x70
TRUSTED_NAME_SOURCE = 0x71
TRUSTED_NAME_NFT_ID = 0x72


class PKIPubKeyUsage(IntEnum):
Expand Down Expand Up @@ -266,22 +271,22 @@ def _provide_trusted_name_common(self, payload: bytes) -> RAPDU:
# pylint: enable=line-too-long

self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))
payload += format_tlv(TrustedNameTag.STRUCT_TYPE, 3) # TrustedName
payload += format_tlv(TrustedNameTag.SIGNER_KEY_ID, 0) # test key
payload += format_tlv(TrustedNameTag.SIGNER_ALGO, 1) # secp256k1
payload += format_tlv(TrustedNameTag.SIGNATURE,
payload += format_tlv(FieldTag.STRUCT_TYPE, 3) # TrustedName
payload += format_tlv(FieldTag.SIGNER_KEY_ID, 0) # test key
payload += format_tlv(FieldTag.SIGNER_ALGO, 1) # secp256k1
payload += format_tlv(FieldTag.DER_SIGNATURE,
sign_data(Key.TRUSTED_NAME, payload))
chunks = self._cmd_builder.provide_trusted_name(payload)
for chunk in chunks[:-1]:
self._exchange(chunk)
return self._exchange(chunks[-1])

def provide_trusted_name_v1(self, addr: bytes, name: str, challenge: int) -> RAPDU:
payload = format_tlv(TrustedNameTag.STRUCT_VERSION, 1)
payload += format_tlv(TrustedNameTag.CHALLENGE, challenge)
payload += format_tlv(TrustedNameTag.COIN_TYPE, 0x3c) # ETH in slip-44
payload += format_tlv(TrustedNameTag.NAME, name)
payload += format_tlv(TrustedNameTag.ADDRESS, addr)
payload = format_tlv(FieldTag.STRUCT_VERSION, 1)
payload += format_tlv(FieldTag.CHALLENGE, challenge)
payload += format_tlv(FieldTag.COIN_TYPE, 0x3c) # ETH in slip-44
payload += format_tlv(FieldTag.TRUSTED_NAME, name)
payload += format_tlv(FieldTag.ADDRESS, addr)
return self._provide_trusted_name_common(payload)

def provide_trusted_name_v2(self,
Expand All @@ -293,19 +298,19 @@ def provide_trusted_name_v2(self,
nft_id: Optional[int] = None,
challenge: Optional[int] = None,
not_valid_after: Optional[tuple[int]] = None) -> RAPDU:
payload = format_tlv(TrustedNameTag.STRUCT_VERSION, 2)
payload += format_tlv(TrustedNameTag.NAME, name)
payload += format_tlv(TrustedNameTag.ADDRESS, addr)
payload += format_tlv(TrustedNameTag.NAME_TYPE, name_type)
payload += format_tlv(TrustedNameTag.NAME_SOURCE, name_source)
payload += format_tlv(TrustedNameTag.CHAIN_ID, chain_id)
payload = format_tlv(FieldTag.STRUCT_VERSION, 2)
payload += format_tlv(FieldTag.TRUSTED_NAME, name)
payload += format_tlv(FieldTag.ADDRESS, addr)
payload += format_tlv(FieldTag.TRUSTED_NAME_TYPE, name_type)
payload += format_tlv(FieldTag.TRUSTED_NAME_SOURCE, name_source)
payload += format_tlv(FieldTag.CHAIN_ID, chain_id)
if nft_id is not None:
payload += format_tlv(TrustedNameTag.NFT_ID, nft_id)
payload += format_tlv(FieldTag.TRUSTED_NAME_NFT_ID, nft_id)
if challenge is not None:
payload += format_tlv(TrustedNameTag.CHALLENGE, challenge)
payload += format_tlv(FieldTag.CHALLENGE, challenge)
if not_valid_after is not None:
assert len(not_valid_after) == 3
payload += format_tlv(TrustedNameTag.NOT_VALID_AFTER, struct.pack("BBB", *not_valid_after))
payload += format_tlv(FieldTag.NOT_VALID_AFTER, struct.pack("BBB", *not_valid_after))
return self._provide_trusted_name_common(payload)

def set_plugin(self,
Expand Down Expand Up @@ -468,3 +473,54 @@ def provide_token_metadata(self,
decimals,
chain_id,
sig))

def _prepare_network_info(self,
name: str,
ticker: str,
chain_id: int,
icon: Optional[bytes] = None) -> bytes:

payload = format_tlv(FieldTag.STRUCT_TYPE, 8)
payload += format_tlv(FieldTag.STRUCT_VERSION, 1)
payload += format_tlv(FieldTag.BLOCKCHAIN_FAMILY, 1)
payload += format_tlv(FieldTag.CHAIN_ID, chain_id.to_bytes(8, 'big'))
payload += format_tlv(FieldTag.NETWORK_NAME, name.encode('utf-8'))
payload += format_tlv(FieldTag.TICKER, ticker.encode('utf-8'))
if icon:
# Network Icon
payload += format_tlv(FieldTag.NETWORK_ICON_HASH, sha256(icon).digest())
# Append the data Signature
payload += format_tlv(FieldTag.DER_SIGNATURE,
sign_data(Key.CAL, payload))
return payload

def provide_network_information(self,
name: str,
ticker: str,
chain_id: int,
icon: Optional[bytes] = None) -> RAPDU:

if self._pki_client is None:
print(f"Ledger-PKI Not supported on '{self._firmware.name}'")
else:
# pylint: disable=line-too-long
if self._firmware == Firmware.NANOSP:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010310040102000015473045022100C15795C2AE41E6FAE6B1362EE1AE216428507D7C1D6939B928559CC7A1F6425C02206139CF2E133DD62F3E00F183E42109C9853AC62B6B70C5079B9A80DBB9D54AB5" # noqa: E501
elif self._firmware == Firmware.NANOX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010215473045022100E3B956F93FBFF0D41908483888F0F75D4714662A692F7A38DC6C41A13294F9370220471991BECB3CA4F43413CADC8FF738A8CC03568BFA832B4DCFE8C469080984E5" # noqa: E501
elif self._firmware == Firmware.STAX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C6405618873401013501041546304402206731FCD3E2432C5CA162381392FD17AD3A41EEF852E1D706F21A656AB165263602204B89FAE8DBAF191E2D79FB00EBA80D613CB7EDF0BE960CB6F6B29D96E1437F5F" # noqa: E501
elif self._firmware == Firmware.FLEX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010515473045022100B59EA8B958AA40578A6FBE9BBFB761020ACD5DBD8AA863C11DA17F42B2AFDE790220186316059EFA58811337D47C7F815F772EA42BBBCEA4AE123D1118C80588F5CB" # noqa: E501
# pylint: enable=line-too-long

self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))

# Add the network info
payload = self._prepare_network_info(name, ticker, chain_id, icon)
chunks = self._cmd_builder.provide_network_information(payload, icon)
for chunk in chunks[:-1]:
response = self._exchange(chunk)
assert response.status == StatusWord.OK
response = self._exchange(chunks[-1])
assert response.status == StatusWord.OK
31 changes: 30 additions & 1 deletion client/src/ledger_app_clients/ethereum/command_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import struct
from enum import IntEnum
from typing import Optional
from typing import List, Optional
from ragger.bip import pack_derivation_path

from .eip712 import EIP712FieldType
Expand All @@ -25,13 +25,16 @@ class InsType(IntEnum):
GET_CHALLENGE = 0x20
PROVIDE_TRUSTED_NAME = 0x22
EXTERNAL_PLUGIN_SETUP = 0x12
PROVIDE_NETWORK_INFORMATION = 0x30


class P1Type(IntEnum):
COMPLETE_SEND = 0x00
PARTIAL_SEND = 0x01
SIGN_FIRST_CHUNK = 0x00
SIGN_SUBSQT_CHUNK = 0x80
FIRST_CHUNK = 0x01
FOLLOWING_CHUNK = 0x00


class P2Type(IntEnum):
Expand All @@ -48,6 +51,8 @@ class P2Type(IntEnum):
FILTERING_TOKEN_ADDR_CHECK = 0xfd
FILTERING_AMOUNT_FIELD = 0xfe
FILTERING_RAW = 0xff
NETWORK_CONFIG = 0x00
NETWORK_ICON = 0x01


class CommandBuilder:
Expand Down Expand Up @@ -408,3 +413,27 @@ def provide_erc20_token_information(self,
0x00,
0x00,
payload)

def provide_network_information(self,
tlv_payload: bytes,
icon: Optional[bytes] = None) -> list[bytes]:
chunks: List[bytes] = []

# Check if the TLV payload is larger than 0xff
assert len(tlv_payload) < 0xff, "Payload too large"
# Serialize the payload
chunks.append(self._serialize(InsType.PROVIDE_NETWORK_INFORMATION,
0x00,
P2Type.NETWORK_CONFIG,
tlv_payload))

if icon:
p1 = P1Type.FIRST_CHUNK
while len(icon) > 0:
chunks.append(self._serialize(InsType.PROVIDE_NETWORK_INFORMATION,
p1,
P2Type.NETWORK_ICON,
icon[:0xff]))
icon = icon[0xff:]
p1 = P1Type.FOLLOWING_CHUNK
return chunks
6 changes: 1 addition & 5 deletions doc/eth_contract_support_embedded.adoc
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
Ethereum application : Embedded Contract Support
= Ethereum application : Embedded Contract Support
================================================
Ledger Firmware Team <[email protected]>
Application version 1.3.0 - 05th of July 2020

## 1.3.0
- Initial release

## About

Expand Down
Loading
Loading