Skip to content

Commit

Permalink
Merge pull request #632 from LedgerHQ/fix/apa/filtering_on_empty_array
Browse files Browse the repository at this point in the history
Fix EIP-712 filtering on empty array
  • Loading branch information
apaillier-ledger authored Sep 11, 2024
2 parents 40deac4 + ab94ac8 commit 78d40c9
Show file tree
Hide file tree
Showing 32 changed files with 524 additions and 134 deletions.
21 changes: 13 additions & 8 deletions client/src/ledger_app_clients/ethereum/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,25 +146,30 @@ def eip712_sign_legacy(self,
def eip712_filtering_activate(self):
return self._exchange_async(self._cmd_builder.eip712_filtering_activate())

def eip712_filtering_discarded_path(self, path: str):
return self._exchange(self._cmd_builder.eip712_filtering_discarded_path(path))

def eip712_filtering_message_info(self, name: str, filters_count: int, sig: bytes):
return self._exchange_async(self._cmd_builder.eip712_filtering_message_info(name,
filters_count,
sig))

def eip712_filtering_amount_join_token(self, token_idx: int, sig: bytes):
def eip712_filtering_amount_join_token(self, token_idx: int, sig: bytes, discarded: bool):
return self._exchange_async(self._cmd_builder.eip712_filtering_amount_join_token(token_idx,
sig))
sig,
discarded))

def eip712_filtering_amount_join_value(self, token_idx: int, name: str, sig: bytes):
def eip712_filtering_amount_join_value(self, token_idx: int, name: str, sig: bytes, discarded: bool):
return self._exchange_async(self._cmd_builder.eip712_filtering_amount_join_value(token_idx,
name,
sig))
sig,
discarded))

def eip712_filtering_datetime(self, name: str, sig: bytes):
return self._exchange_async(self._cmd_builder.eip712_filtering_datetime(name, sig))
def eip712_filtering_datetime(self, name: str, sig: bytes, discarded: bool):
return self._exchange_async(self._cmd_builder.eip712_filtering_datetime(name, sig, discarded))

def eip712_filtering_raw(self, name: str, sig: bytes):
return self._exchange_async(self._cmd_builder.eip712_filtering_raw(name, sig))
def eip712_filtering_raw(self, name: str, sig: bytes, discarded: bool):
return self._exchange_async(self._cmd_builder.eip712_filtering_raw(name, sig, discarded))

def sign(self,
bip32_path: str,
Expand Down
26 changes: 18 additions & 8 deletions client/src/ledger_app_clients/ethereum/command_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class P2Type(IntEnum):
LEGACY_IMPLEM = 0x00
NEW_IMPLEM = 0x01
FILTERING_ACTIVATE = 0x00
FILTERING_DISCARDED_PATH = 0x01
FILTERING_MESSAGE_INFO = 0x0f
FILTERING_DATETIME = 0xfc
FILTERING_TOKEN_ADDR_CHECK = 0xfd
Expand Down Expand Up @@ -164,6 +165,15 @@ def _eip712_filtering_send_name(self, name: str, sig: bytes) -> bytes:
data += sig
return data

def eip712_filtering_discarded_path(self, path: str) -> bytes:
data = bytearray()
data.append(len(path))
data += path.encode()
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.COMPLETE_SEND,
P2Type.FILTERING_DISCARDED_PATH,
data)

def eip712_filtering_message_info(self, name: str, filters_count: int, sig: bytes) -> bytes:
data = bytearray()
data.append(len(name))
Expand All @@ -176,37 +186,37 @@ def eip712_filtering_message_info(self, name: str, filters_count: int, sig: byte
P2Type.FILTERING_MESSAGE_INFO,
data)

def eip712_filtering_amount_join_token(self, token_idx: int, sig: bytes) -> bytes:
def eip712_filtering_amount_join_token(self, token_idx: int, sig: bytes, discarded: bool) -> bytes:
data = bytearray()
data.append(token_idx)
data.append(len(sig))
data += sig
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.COMPLETE_SEND,
int(discarded),
P2Type.FILTERING_TOKEN_ADDR_CHECK,
data)

def eip712_filtering_amount_join_value(self, token_idx: int, name: str, sig: bytes) -> bytes:
def eip712_filtering_amount_join_value(self, token_idx: int, name: str, sig: bytes, discarded: bool) -> bytes:
data = bytearray()
data.append(len(name))
data += name.encode()
data.append(token_idx)
data.append(len(sig))
data += sig
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.COMPLETE_SEND,
int(discarded),
P2Type.FILTERING_AMOUNT_FIELD,
data)

def eip712_filtering_datetime(self, name: str, sig: bytes) -> bytes:
def eip712_filtering_datetime(self, name: str, sig: bytes, discarded: bool) -> bytes:
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.COMPLETE_SEND,
int(discarded),
P2Type.FILTERING_DATETIME,
self._eip712_filtering_send_name(name, sig))

def eip712_filtering_raw(self, name: str, sig: bytes) -> bytes:
def eip712_filtering_raw(self, name: str, sig: bytes, discarded: bool) -> bytes:
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.COMPLETE_SEND,
int(discarded),
P2Type.FILTERING_RAW,
self._eip712_filtering_send_name(name, sig))

Expand Down
80 changes: 41 additions & 39 deletions client/src/ledger_app_clients/ethereum/eip712/InputData.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,32 +194,36 @@ def encode_bytes_dyn(value: str, typesize: int) -> bytes:
encoding_functions[EIP712FieldType.DYN_BYTES] = encode_bytes_dyn


def send_filter(path: str, discarded: bool):
assert path in filtering_paths.keys()

if filtering_paths[path]["type"] == "amount_join_token":
send_filtering_amount_join_token(path, filtering_paths[path]["token"], discarded)
elif filtering_paths[path]["type"] == "amount_join_value":
if "token" in filtering_paths[path].keys():
token = filtering_paths[path]["token"]
else:
# Permit (ERC-2612)
token = 0xff
send_filtering_amount_join_value(path, token, filtering_paths[path]["name"], discarded)
elif filtering_paths[path]["type"] == "datetime":
send_filtering_datetime(path, filtering_paths[path]["name"], discarded)
elif filtering_paths[path]["type"] == "raw":
send_filtering_raw(path, filtering_paths[path]["name"], discarded)
else:
assert False


def send_struct_impl_field(value, field):
# Something wrong happened if this triggers
if isinstance(value, list) or (field["enum"] == EIP712FieldType.CUSTOM):
breakpoint()
assert not isinstance(value, list)
assert field["enum"] != EIP712FieldType.CUSTOM

data = encoding_functions[field["enum"]](value, field["typesize"])

if filtering_paths:
path = ".".join(current_path)
if path in filtering_paths.keys():
if filtering_paths[path]["type"] == "amount_join_token":
send_filtering_amount_join_token(filtering_paths[path]["token"])
elif filtering_paths[path]["type"] == "amount_join_value":
if "token" in filtering_paths[path].keys():
token = filtering_paths[path]["token"]
else:
# Permit (ERC-2612)
token = 0xff
send_filtering_amount_join_value(token,
filtering_paths[path]["name"])
elif filtering_paths[path]["type"] == "datetime":
send_filtering_datetime(filtering_paths[path]["name"])
elif filtering_paths[path]["type"] == "raw":
send_filtering_raw(filtering_paths[path]["name"])
else:
assert False
send_filter(path, False)

with app_client.eip712_send_struct_impl_struct_field(data):
enable_autonext()
Expand All @@ -234,6 +238,12 @@ def evaluate_field(structs, data, field, lvls_left, new_level=True):
if len(array_lvls) > 0 and lvls_left > 0:
with app_client.eip712_send_struct_impl_array(len(data)):
pass
if len(data) == 0:
for path in filtering_paths.keys():
dpath = ".".join(current_path) + ".[]"
if path.startswith(dpath):
app_client.eip712_filtering_discarded_path(path)
send_filter(path, True)
idx = 0
for subdata in data:
current_path.append("[]")
Expand Down Expand Up @@ -295,57 +305,49 @@ def send_filtering_message_info(display_name: str, filters_count: int):
disable_autonext()


def send_filtering_amount_join_token(token_idx: int):
def send_filtering_amount_join_token(path: str, token_idx: int, discarded: bool):
global sig_ctx

path_str = ".".join(current_path)

to_sign = start_signature_payload(sig_ctx, 11)
to_sign += path_str.encode()
to_sign += path.encode()
to_sign.append(token_idx)
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
with app_client.eip712_filtering_amount_join_token(token_idx, sig):
with app_client.eip712_filtering_amount_join_token(token_idx, sig, discarded):
pass


def send_filtering_amount_join_value(token_idx: int, display_name: str):
def send_filtering_amount_join_value(path: str, token_idx: int, display_name: str, discarded: bool):
global sig_ctx

path_str = ".".join(current_path)

to_sign = start_signature_payload(sig_ctx, 22)
to_sign += path_str.encode()
to_sign += path.encode()
to_sign += display_name.encode()
to_sign.append(token_idx)
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
with app_client.eip712_filtering_amount_join_value(token_idx, display_name, sig):
with app_client.eip712_filtering_amount_join_value(token_idx, display_name, sig, discarded):
pass


def send_filtering_datetime(display_name: str):
def send_filtering_datetime(path: str, display_name: str, discarded: bool):
global sig_ctx

path_str = ".".join(current_path)

to_sign = start_signature_payload(sig_ctx, 33)
to_sign += path_str.encode()
to_sign += path.encode()
to_sign += display_name.encode()
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
with app_client.eip712_filtering_datetime(display_name, sig):
with app_client.eip712_filtering_datetime(display_name, sig, discarded):
pass


# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
def send_filtering_raw(display_name):
def send_filtering_raw(path: str, display_name: str, discarded: bool):
global sig_ctx

path_str = ".".join(current_path)

to_sign = start_signature_payload(sig_ctx, 72)
to_sign += path_str.encode()
to_sign += path.encode()
to_sign += display_name.encode()
sig = keychain.sign_data(keychain.Key.CAL, to_sign)
with app_client.eip712_filtering_raw(display_name, sig):
with app_client.eip712_filtering_raw(display_name, sig, discarded):
pass


Expand Down
26 changes: 24 additions & 2 deletions doc/ethapp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Application version 1.9.19 - 2022-05-17
- Add EIP-712 amount & date/time filtering
- PROVIDE ERC 20 TOKEN INFORMATION & PROVIDE NFT INFORMATION now send back the index where the asset has been stored

### 1.12.0
- Add EIP-712 discarded filter path support

## About

This application describes the APDU messages interface to communicate with the Ethereum application.
Expand Down Expand Up @@ -828,6 +831,12 @@ Field substitution will be ignored if the full filtering is not activated.

This command should come before the domain & message implementations. If activated, fields will be by default hidden unless they receive a field name substitution.

##### Discarded filter path

This command gives the app the absolute path of the upcoming filter which will be discarded (because it targets a field within an empty array).

The next filter should be marked as discarded (with P1) to be able to use this given filter path.

##### Message info

This command should come right after the implementation of the domain has been sent with *SEND STRUCT IMPLEMENTATION*, just before sending the message implementation.
Expand Down Expand Up @@ -880,8 +889,11 @@ _Command_
[width="80%"]
|=========================================================================
| *CLA* | *INS* | *P1* | *P2* | *LC* | *Le*
| E0 | 1E | 00
| 00 : activation
| E0 | 1E | 00 : standard

01 : discarded | 00 : activation

01 : discarded filter path

0F : message info

Expand All @@ -901,6 +913,16 @@ _Input data_

None

##### If P2 == discarded filter path

[width="80%"]
|==========================================
| *Description* | *Length (byte)*
| Path length | 1
| Path | variable
|==========================================


##### If P2 == message info

[width="80%"]
Expand Down
2 changes: 1 addition & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ static uint16_t handleApdu(command_t *cmd, uint32_t *flags, uint32_t *tx) {
break;

case INS_EIP712_FILTERING:
sw = handle_eip712_filtering(cmd->p2, cmd->data, cmd->lc, flags);
sw = handle_eip712_filtering(cmd->p1, cmd->p2, cmd->data, cmd->lc, flags);
break;
#endif // HAVE_EIP712_FULL_SUPPORT

Expand Down
Loading

0 comments on commit 78d40c9

Please sign in to comment.