Skip to content

Commit 941aae8

Browse files
Improve EIP-7702 tests with web3 v7
1 parent 4b40bc2 commit 941aae8

File tree

4 files changed

+70
-68
lines changed

4 files changed

+70
-68
lines changed

client/src/ledger_app_clients/ethereum/client.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,10 @@ def eip712_filtering_trusted_name(self,
213213
def eip712_filtering_raw(self, name: str, sig: bytes, discarded: bool):
214214
return self._exchange_async(self._cmd_builder.eip712_filtering_raw(name, sig, discarded))
215215

216-
def serialize_tx(self, tx_params: dict, tx_raw: Optional[bytes] = None) -> tuple[bytes, bytes]:
216+
def serialize_tx(self, tx_params: dict) -> tuple[bytes, bytes]:
217217
"""Computes the serialized TX and its hash"""
218218

219-
if tx_raw is not None:
220-
tx = tx_raw
221-
else:
222-
tx = Web3().eth.account.create().sign_transaction(tx_params).raw_transaction
219+
tx = Web3().eth.account.create().sign_transaction(tx_params).raw_transaction
223220
prefix = bytes()
224221
suffix = []
225222
if tx[0] in [0x01, 0x02, 0x04]:
@@ -228,20 +225,16 @@ def serialize_tx(self, tx_params: dict, tx_raw: Optional[bytes] = None) -> tuple
228225
else: # legacy
229226
if "chainId" in tx_params:
230227
suffix = [int(tx_params["chainId"]), bytes(), bytes()]
231-
if tx_raw is None:
232-
decoded_tx = rlp.decode(tx)[:-3] # remove already computed signature
233-
else:
234-
decoded_tx = rlp.decode(tx)
228+
decoded_tx = rlp.decode(tx)[:-3] # remove already computed signature
235229
encoded_tx = prefix + rlp.encode(decoded_tx + suffix)
236230
tx_hash = keccak(encoded_tx)
237231
return encoded_tx, tx_hash
238232

239233
def sign(self,
240234
bip32_path: str,
241235
tx_params: dict,
242-
tx_raw: Optional[bytes] = None, # Introduced for 7702 until web3.py supports authorization lists
243236
mode: SignMode = SignMode.BASIC):
244-
tx, _ = self.serialize_tx(tx_params, tx_raw)
237+
tx, _ = self.serialize_tx(tx_params)
245238
chunks = self._cmd_builder.sign(bip32_path, tx, mode)
246239
for chunk in chunks[:-1]:
247240
self._exchange(chunk)

client/src/ledger_app_clients/ethereum/utils.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from typing import Optional
21
from eth_account import Account
32
from eth_account.messages import encode_defunct, encode_typed_data
3+
from eth_account.datastructures import SignedSetCodeAuthorization
4+
from eth_account.typed_transactions.set_code_transaction import Authorization
5+
from eth_keys.datatypes import Signature
46
import rlp
57

68

@@ -18,11 +20,8 @@ def recover_message(msg, vrs: tuple[int, int, int]) -> bytes:
1820
return bytes.fromhex(addr[2:])
1921

2022

21-
def recover_transaction(tx_params, vrs: tuple[int, int, int], raw_tx_param: Optional[bytes] = None) -> bytes:
22-
if raw_tx_param is None:
23-
raw_tx = Account.create().sign_transaction(tx_params).raw_transaction
24-
else:
25-
raw_tx = raw_tx_param
23+
def recover_transaction(tx_params, vrs: tuple[int, int, int]) -> bytes:
24+
raw_tx = Account.create().sign_transaction(tx_params).raw_transaction
2625
prefix = bytes()
2726
if raw_tx[0] in [0x01, 0x02, 0x04]:
2827
prefix = raw_tx[:1]
@@ -53,9 +52,30 @@ def recover_transaction(tx_params, vrs: tuple[int, int, int], raw_tx_param: Opti
5352
# Pre EIP-155 TX
5453
assert False
5554
decoded = rlp.decode(raw_tx)
56-
if raw_tx_param is None:
57-
reencoded = rlp.encode(decoded[:-3] + list(vrs))
58-
else:
59-
reencoded = rlp.encode(decoded + list(vrs))
55+
reencoded = rlp.encode(decoded[:-3] + list(vrs))
6056
addr = Account.recover_transaction(prefix + reencoded)
6157
return bytes.fromhex(addr[2:])
58+
59+
60+
# Code inspired by :
61+
# https://github.com/ethereum/eth-account/blob/a1ba20c9a112d3534ac3296f21f51e2f5127bf9b/eth_account/account.py#L1057
62+
def get_authorization_obj(chain_id: int,
63+
nonce: int,
64+
address: bytes,
65+
vrs: tuple[int, int, int]) -> SignedSetCodeAuthorization:
66+
unsigned_authorization = Authorization(chain_id, address, nonce)
67+
sig = Signature(vrs=vrs)
68+
return SignedSetCodeAuthorization(
69+
chain_id=chain_id,
70+
address=address,
71+
nonce=nonce,
72+
y_parity=sig.v,
73+
r=sig.r,
74+
s=sig.s,
75+
signature=sig,
76+
authorization_hash=unsigned_authorization.hash(),
77+
)
78+
79+
80+
def recover_authorization(chain_id: int, nonce: int, address: bytes, vrs: tuple[int, int, int]) -> bytes:
81+
return get_authorization_obj(chain_id, nonce, address, vrs).authority

tests/ragger/test_eip7702.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
from typing import Optional
34
from ragger.error import ExceptionRAPDU
45
from ragger.backend import BackendInterface
56
from ragger.firmware import Firmware
@@ -11,6 +12,8 @@
1112
import client.response_parser as ResponseParser
1213
from client.tx_auth_7702 import TxAuth7702
1314

15+
from client.utils import recover_authorization
16+
1417
BIP32_PATH = "m/44'/60'/0'/0/0"
1518
TEST_ADDRESS_0 = bytes.fromhex("00" * 20)
1619
TEST_ADDRESS_1 = bytes.fromhex("01" * 20)
@@ -24,6 +27,8 @@
2427
NONCE = 1337
2528
NONCE_MAX = 0xFFFFFFFFFFFFFFFF
2629

30+
DEVICE_ADDR: Optional[bytes] = None
31+
2732
# Test vectors computed with
2833
# cast wallet sign-auth $ADDRESS --mnemonic $MNEMONIC --mnemonic-derivation-path "m/44'/60'/0'/0/0" --nonce $NONCE --chain $CHAINID
2934
# Decoded by https://codechain-io.github.io/rlp-debugger/
@@ -35,23 +40,25 @@ def common(firmware: Firmware,
3540
test_name: str,
3641
delegate: bytes,
3742
nonce: int,
38-
chain_id: int,
39-
v: int = None,
40-
r: int = None,
41-
s: int = None):
43+
chain_id: int):
4244

45+
global DEVICE_ADDR
4346
app_client = EthAppClient(backend)
4447

4548
if firmware.is_nano:
4649
end_text = "Accept"
4750
else:
4851
end_text = ".*Sign.*"
4952

53+
if DEVICE_ADDR is None:
54+
with app_client.get_public_addr(bip32_path=BIP32_PATH, display=False):
55+
pass
56+
_, DEVICE_ADDR, _ = ResponseParser.pk_addr(app_client.response().data)
5057
auth_params = TxAuth7702(delegate, nonce, chain_id)
5158
with app_client.sign_eip7702_authorization(BIP32_PATH, auth_params):
5259
scenario.review_approve(test_name=test_name, custom_screen_text=end_text)
5360
vrs = ResponseParser.signature(app_client.response().data)
54-
assert vrs == (v, r, s)
61+
assert recover_authorization(chain_id, nonce, delegate, vrs) == DEVICE_ADDR
5562

5663

5764
def common_rejected(firmware: Firmware,
@@ -95,10 +102,7 @@ def test_eip7702_in_whitelist(firmware: Firmware,
95102
test_name,
96103
TEST_ADDRESS_1,
97104
NONCE,
98-
CHAIN_ID_1,
99-
0x00,
100-
0xf82e50a755fa989f4bb9b36b15afb4424ce9cda69752fd17a7eb14737d963e62,
101-
0x07c9c91d6140b45ea52f29de7a5effb9dd340607a26e225c40278e91c4054492)
105+
CHAIN_ID_1)
102106

103107

104108
def test_eip7702_in_whitelist_all_chain_whitelisted(firmware: Firmware,
@@ -114,10 +118,7 @@ def test_eip7702_in_whitelist_all_chain_whitelisted(firmware: Firmware,
114118
test_name,
115119
TEST_ADDRESS_0,
116120
NONCE,
117-
CHAIN_ID_2,
118-
0x00,
119-
0x0378f7ac482ec728b65d19d03943bbb3fe7307c72c646e7d2d0c11bee81eb2b9,
120-
0x332266ec3ef996bf835c50a833006b4c80398d597d0e684619db4d51a384a38d)
121+
CHAIN_ID_2)
121122

122123

123124
def test_eip7702_in_whitelist_all_chain_param(firmware: Firmware,
@@ -133,10 +134,7 @@ def test_eip7702_in_whitelist_all_chain_param(firmware: Firmware,
133134
test_name,
134135
TEST_ADDRESS_2,
135136
NONCE,
136-
CHAIN_ID_0,
137-
0x01,
138-
0xa24f35cafc6b408ce32539d4bd89a67edd4d6303fc676dfddf93b98405b7ee5e,
139-
0x159456babe656692959ca3d829ca269e8f82387c91e40a33633d190dda7a3c5c)
137+
CHAIN_ID_0)
140138

141139

142140
def test_eip7702_in_whitelist_max(firmware: Firmware,
@@ -152,10 +150,7 @@ def test_eip7702_in_whitelist_max(firmware: Firmware,
152150
test_name,
153151
TEST_ADDRESS_MAX,
154152
NONCE_MAX,
155-
CHAIN_ID_MAX,
156-
0x00,
157-
0xa07c680894498c21e80febb011ae5d62ede6645d77d7c902db065d5a082dd220,
158-
0x6b5c62225cf65a6240c2583dacc1641c8d692ed3bd00473cc3e28fe1a4f52294)
153+
CHAIN_ID_MAX)
159154

160155

161156
def test_eip7702_in_whitelist_wrong_chain(firmware: Firmware,

tests/ragger/test_sign.py

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from client.client import EthAppClient, StatusWord
1313
import client.response_parser as ResponseParser
1414
from client.settings import SettingID, settings_toggle
15-
from client.utils import recover_transaction
15+
from client.utils import recover_transaction, get_authorization_obj
1616

1717

1818
# Values used across all tests
@@ -38,13 +38,9 @@ def common(firmware: Firmware,
3838
scenario_navigator: NavigateWithScenario,
3939
default_screenshot_path: Path,
4040
tx_params: dict,
41-
tx_raw: bytes = None,
4241
test_name: str = "",
4342
path: str = BIP32_PATH,
44-
with_simu: bool = False,
45-
v: bytes = None,
46-
r: bytes = None,
47-
s: bytes = None):
43+
with_simu: bool = False):
4844
app_client = EthAppClient(backend)
4945

5046
if tx_params["chainId"] == 3:
@@ -71,7 +67,7 @@ def common(firmware: Firmware,
7167
pass
7268
_, DEVICE_ADDR, _ = ResponseParser.pk_addr(app_client.response().data)
7369

74-
with app_client.sign(path, tx_params, tx_raw):
70+
with app_client.sign(path, tx_params):
7571
if with_simu:
7672
navigator.navigate_and_compare(default_screenshot_path,
7773
f"{test_name}/warning",
@@ -85,17 +81,8 @@ def common(firmware: Firmware,
8581

8682
# verify signature
8783
vrs = ResponseParser.signature(app_client.response().data)
88-
if v is not None: # temporary until web3.py supports 7702
89-
print(vrs)
90-
print(v)
91-
print(r)
92-
print(s)
93-
assert v == vrs[0]
94-
assert r == vrs[1]
95-
assert s == vrs[2]
96-
else:
97-
addr = recover_transaction(tx_params, vrs, tx_raw)
98-
assert addr == DEVICE_ADDR
84+
addr = recover_transaction(tx_params, vrs)
85+
assert addr == DEVICE_ADDR
9986

10087

10188
def common_reject(backend: BackendInterface,
@@ -376,19 +363,26 @@ def test_sign_eip_7702(firmware: Firmware,
376363
if firmware == Firmware.NANOS:
377364
pytest.skip("Not supported on LNS")
378365
tx_params = {
379-
"chainId": 1
366+
"chainId": 1,
367+
"nonce": 1337,
368+
"maxPriorityFeePerGas": 0x01,
369+
"maxFeePerGas": 0x01,
370+
"gas": 21000,
371+
"to": bytes.fromhex("1212121212121212121212121212121212121212"),
372+
"value": Web3.to_wei(0.01, "ether"),
373+
"authorizationList": [
374+
get_authorization_obj(0, 1337, bytes.fromhex("1212121212121212121212121212121212121212"), (
375+
0x01,
376+
0xa24f35cafc6b408ce32539d4bd89a67edd4d6303fc676dfddf93b98405b7ee5e,
377+
0x159456babe656692959ca3d829ca269e8f82387c91e40a33633d190dda7a3c5c,
378+
))
379+
],
380380
}
381-
tx_raw = bytes.fromhex("04f888018205390101825208941212121212121212121212121212121212121212872386f26fc1000080c0f85ef85c8094020202020202020202020202020202020202020282053901a0a24f35cafc6b408ce32539d4bd89a67edd4d6303fc676dfddf93b98405b7ee5ea0159456babe656692959ca3d829ca269e8f82387c91e40a33633d190dda7a3c5c")
382381
common(firmware,
383382
backend,
384383
navigator,
385384
scenario_navigator,
386385
default_screenshot_path,
387386
tx_params,
388-
tx_raw,
389387
test_name=test_name,
390-
path=BIP32_PATH,
391-
with_simu=False,
392-
v=0x01,
393-
r=0x9800ce04ecb46aebde0fea80c66392255eb13c3b3597aeec222447f6e1bfcfaf,
394-
s=0x787eb82581e1a51d8956f21a0ccd0b96d8de14b04c11de0b7dcb5e400219902b)
388+
path=BIP32_PATH)

0 commit comments

Comments
 (0)