Initial EIP1559 implementation Fix a few small issues Progress on Python lib implementation and firmware Fix RLP length Start fixing tests Fix legacy transactions Simplify API and logic Add EIP1559 tests Fix access list formatting Fix UI visiblity issue Fix commented out code fix: correct linting issues Fix access_list protobuf formatting Remove unneeded code Remove dead code Check tx_type bounds for EIP 2718 Reduce code duplication Prefer eip2718_type over re-using tx_type Add more tests Simplify format_access_list Simplify sign_tx slightly Change Access List format and add logic to encode it Fix a bunch of small PR comments Fix a linting issue Move tests out of class and regenerate Remove copy-pasted comments Add access list to CLI Simplify _parse_access_list_item Fix small mistakes following rebase Fix linting Refactor to use a separate message for EIP 1559 tx Simplify changed legacy code Fix a few small PR comments Fix linting fix(legacy): recognize SignTxEIP1559 on legacy build Fix PR commentspull/1653/head
parent
69564a9a79
commit
38fa9197ca
@ -0,0 +1 @@
|
||||
Support for Ethereum EIP1559 transactions
|
@ -0,0 +1,154 @@
|
||||
from trezor import wire
|
||||
from trezor.crypto import rlp
|
||||
from trezor.crypto.curve import secp256k1
|
||||
from trezor.crypto.hashlib import sha3_256
|
||||
from trezor.messages import EthereumAccessList, EthereumSignTxEIP1559, EthereumTxRequest
|
||||
from trezor.utils import HashWriter
|
||||
|
||||
from apps.common import paths
|
||||
|
||||
from . import address
|
||||
from .keychain import with_keychain_from_chain_id
|
||||
from .layout import (
|
||||
require_confirm_data,
|
||||
require_confirm_eip1559_fee,
|
||||
require_confirm_tx,
|
||||
)
|
||||
from .sign_tx import check_data, check_to, handle_erc20, sanitize, send_request_chunk
|
||||
|
||||
TX_TYPE = 2
|
||||
|
||||
|
||||
def access_list_item_length(item: EthereumAccessList) -> int:
|
||||
address_length = rlp.length(address.bytes_from_address(item.address))
|
||||
keys_length = rlp.length(item.storage_keys)
|
||||
return (
|
||||
rlp.header_length(address_length + keys_length) + address_length + keys_length
|
||||
)
|
||||
|
||||
|
||||
def access_list_length(access_list: list[EthereumAccessList]) -> int:
|
||||
payload_length = sum(access_list_item_length(i) for i in access_list)
|
||||
return rlp.header_length(payload_length) + payload_length
|
||||
|
||||
|
||||
def write_access_list(w: HashWriter, access_list: list[EthereumAccessList]) -> None:
|
||||
payload_length = sum(access_list_item_length(i) for i in access_list)
|
||||
rlp.write_header(w, payload_length, rlp.LIST_HEADER_BYTE)
|
||||
for item in access_list:
|
||||
address_bytes = address.bytes_from_address(item.address)
|
||||
address_length = rlp.length(address_bytes)
|
||||
keys_length = rlp.length(item.storage_keys)
|
||||
rlp.write_header(w, address_length + keys_length, rlp.LIST_HEADER_BYTE)
|
||||
rlp.write(w, address_bytes)
|
||||
rlp.write(w, item.storage_keys)
|
||||
|
||||
|
||||
@with_keychain_from_chain_id
|
||||
async def sign_tx_eip1559(ctx, msg, keychain):
|
||||
msg = sanitize(msg)
|
||||
|
||||
check(msg)
|
||||
|
||||
await paths.validate_path(ctx, keychain, msg.address_n)
|
||||
|
||||
# Handle ERC20s
|
||||
token, address_bytes, recipient, value = await handle_erc20(ctx, msg)
|
||||
|
||||
data_total = msg.data_length
|
||||
|
||||
await require_confirm_tx(ctx, recipient, value, msg.chain_id, token)
|
||||
if token is None and msg.data_length > 0:
|
||||
await require_confirm_data(ctx, msg.data_initial_chunk, data_total)
|
||||
|
||||
await require_confirm_eip1559_fee(
|
||||
ctx,
|
||||
int.from_bytes(msg.max_priority_fee, "big"),
|
||||
int.from_bytes(msg.max_gas_fee, "big"),
|
||||
int.from_bytes(msg.gas_limit, "big"),
|
||||
msg.chain_id,
|
||||
)
|
||||
|
||||
data = bytearray()
|
||||
data += msg.data_initial_chunk
|
||||
data_left = data_total - len(msg.data_initial_chunk)
|
||||
|
||||
total_length = get_total_length(msg, data_total)
|
||||
|
||||
sha = HashWriter(sha3_256(keccak=True))
|
||||
|
||||
rlp.write(sha, TX_TYPE)
|
||||
|
||||
rlp.write_header(sha, total_length, rlp.LIST_HEADER_BYTE)
|
||||
|
||||
for field in (
|
||||
msg.chain_id,
|
||||
msg.nonce,
|
||||
msg.max_priority_fee,
|
||||
msg.max_gas_fee,
|
||||
msg.gas_limit,
|
||||
address_bytes,
|
||||
msg.value,
|
||||
):
|
||||
rlp.write(sha, field)
|
||||
|
||||
if data_left == 0:
|
||||
rlp.write(sha, data)
|
||||
else:
|
||||
rlp.write_header(sha, data_total, rlp.STRING_HEADER_BYTE, data)
|
||||
sha.extend(data)
|
||||
|
||||
while data_left > 0:
|
||||
resp = await send_request_chunk(ctx, data_left)
|
||||
data_left -= len(resp.data_chunk)
|
||||
sha.extend(resp.data_chunk)
|
||||
|
||||
write_access_list(sha, msg.access_list)
|
||||
|
||||
digest = sha.get_digest()
|
||||
result = sign_digest(msg, keychain, digest)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_total_length(msg: EthereumSignTxEIP1559, data_total: int) -> int:
|
||||
length = 0
|
||||
|
||||
for item in (
|
||||
msg.nonce,
|
||||
msg.gas_limit,
|
||||
address.bytes_from_address(msg.to),
|
||||
msg.value,
|
||||
msg.chain_id,
|
||||
msg.max_gas_fee,
|
||||
msg.max_priority_fee,
|
||||
):
|
||||
length += rlp.length(item)
|
||||
|
||||
length += rlp.header_length(data_total, msg.data_initial_chunk)
|
||||
length += data_total
|
||||
|
||||
length += access_list_length(msg.access_list)
|
||||
|
||||
return length
|
||||
|
||||
|
||||
def sign_digest(msg: EthereumSignTxEIP1559, keychain, digest):
|
||||
node = keychain.derive(msg.address_n)
|
||||
signature = secp256k1.sign(
|
||||
node.private_key(), digest, False, secp256k1.CANONICAL_SIG_ETHEREUM
|
||||
)
|
||||
|
||||
req = EthereumTxRequest()
|
||||
req.signature_v = signature[0] - 27
|
||||
req.signature_r = signature[1:33]
|
||||
req.signature_s = signature[33:]
|
||||
|
||||
return req
|
||||
|
||||
|
||||
def check(msg: EthereumSignTxEIP1559):
|
||||
check_data(msg)
|
||||
|
||||
if not check_to(msg):
|
||||
raise wire.DataError("Safety check failed")
|
@ -0,0 +1 @@
|
||||
Support for Ethereum EIP1559 transactions
|
@ -0,0 +1,308 @@
|
||||
# This file is part of the Trezor project.
|
||||
#
|
||||
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
||||
#
|
||||
# This library is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3
|
||||
# as published by the Free Software Foundation.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import ethereum, messages
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
TO_ADDR = "0x1d1c328764a41bda0492b66baa30c4a339ff85ef"
|
||||
|
||||
|
||||
pytestmark = [pytest.mark.altcoin, pytest.mark.ethereum, pytest.mark.skip_t1]
|
||||
|
||||
|
||||
def test_ethereum_signtx_nodata(client):
|
||||
with client:
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/100"),
|
||||
nonce=0,
|
||||
gas_limit=20,
|
||||
to=TO_ADDR,
|
||||
chain_id=1,
|
||||
value=10,
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
)
|
||||
|
||||
assert sig_v == 1
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "2ceeaabc994fbce2fbd66551f9d48fc711c8db2a12e93779eeddede11e41f636"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "2db4a9ecc73da91206f84397ae9287a399076fdc01ed7f3c6554b1c57c39bf8c"
|
||||
)
|
||||
|
||||
|
||||
def test_ethereum_signtx_data(client):
|
||||
with client:
|
||||
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/0"),
|
||||
nonce=0,
|
||||
gas_limit=20,
|
||||
chain_id=1,
|
||||
to=TO_ADDR,
|
||||
value=10,
|
||||
data=b"abcdefghijklmnop" * 16,
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
)
|
||||
assert sig_v == 0
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "8e4361e40e76a7cab17e0a982724bbeaf5079cd02d50c20d431ba7dde2404ea4"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "411930f091bb508e593e22a9ee45bd4d9eeb504ac398123aec889d5951bdebc3"
|
||||
)
|
||||
|
||||
with client:
|
||||
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/0"),
|
||||
nonce=123456,
|
||||
gas_limit=20000,
|
||||
to=TO_ADDR,
|
||||
chain_id=1,
|
||||
value=12345678901234567890,
|
||||
data=b"ABCDEFGHIJKLMNOP" * 256 + b"!!!",
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
)
|
||||
assert sig_v == 0
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "2e4f4c0e7c4e51270b891480060712e9d3bcab01e8ad0fadf2dfddd71504ca94"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "2599beb32757a144dedc82b79153c21269c9939a9245342bcf35764115b62bc1"
|
||||
)
|
||||
|
||||
|
||||
def test_ethereum_signtx_access_list(client):
|
||||
with client:
|
||||
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/100"),
|
||||
nonce=0,
|
||||
gas_limit=20,
|
||||
to=TO_ADDR,
|
||||
chain_id=1,
|
||||
value=10,
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
access_list=[
|
||||
messages.EthereumAccessList(
|
||||
address="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
|
||||
storage_keys=[
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000003"
|
||||
),
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000007"
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
assert sig_v == 1
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "9f8763f3ff8d4d409f6b96bc3f1d84dd504e2c667b162778508478645401f121"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "51e30b68b9091cf8138c07380c4378c2711779b68b2e5264d141479f13a12f57"
|
||||
)
|
||||
|
||||
|
||||
def test_ethereum_signtx_access_list_larger(client):
|
||||
with client:
|
||||
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/100"),
|
||||
nonce=0,
|
||||
gas_limit=20,
|
||||
to=TO_ADDR,
|
||||
chain_id=1,
|
||||
value=10,
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
access_list=[
|
||||
messages.EthereumAccessList(
|
||||
address="0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
|
||||
storage_keys=[
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000003"
|
||||
),
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000007"
|
||||
),
|
||||
],
|
||||
),
|
||||
messages.EthereumAccessList(
|
||||
address="0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
storage_keys=[
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000006"
|
||||
),
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000007"
|
||||
),
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000009"
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
assert sig_v == 1
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "718a3a30827c979975c846d2f60495310c4959ee3adce2d89e0211785725465c"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "7d0ea2a28ef5702ca763c1f340427c0020292ffcbb4553dd1c8ea8e2b9126dbc"
|
||||
)
|
||||
|
||||
|
||||
def test_ethereum_signtx_known_erc20_token(client):
|
||||
with client:
|
||||
|
||||
data = bytearray()
|
||||
# method id signalizing `transfer(address _to, uint256 _value)` function
|
||||
data.extend(bytes.fromhex("a9059cbb"))
|
||||
# 1st function argument (to - the receiver)
|
||||
data.extend(
|
||||
bytes.fromhex(
|
||||
"000000000000000000000000574bbb36871ba6b78e27f4b4dcfb76ea0091880b"
|
||||
)
|
||||
)
|
||||
# 2nd function argument (value - amount to be transferred)
|
||||
data.extend(
|
||||
bytes.fromhex(
|
||||
"000000000000000000000000000000000000000000000000000000000bebc200"
|
||||
)
|
||||
)
|
||||
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/0"),
|
||||
nonce=0,
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
gas_limit=20,
|
||||
# ADT token address
|
||||
to="0xd0d6d6c5fe4a677d343cc433536bb717bae167dd",
|
||||
chain_id=1,
|
||||
# value needs to be 0, token value is set in the contract (data)
|
||||
value=0,
|
||||
data=data,
|
||||
)
|
||||
|
||||
assert sig_v == 1
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "94d67bacb7966f881339d91103f5d738d9c491fff4c01a6513c554ab15e86cc0"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "405bd19a7bf4ae62d41fcb7844e36c786b106b456185c3d0877a7ce7eab6c751"
|
||||
)
|
||||
|
||||
|
||||
def test_ethereum_signtx_unknown_erc20_token(client):
|
||||
with client:
|
||||
data = bytearray()
|
||||
# method id signalizing `transfer(address _to, uint256 _value)` function
|
||||
data.extend(bytes.fromhex("a9059cbb"))
|
||||
# 1st function argument (to - the receiver)
|
||||
data.extend(
|
||||
bytes.fromhex(
|
||||
"000000000000000000000000574bbb36871ba6b78e27f4b4dcfb76ea0091880b"
|
||||
)
|
||||
)
|
||||
# 2nd function argument (value - amount to be transferred)
|
||||
data.extend(
|
||||
bytes.fromhex(
|
||||
"0000000000000000000000000000000000000000000000000000000000000123"
|
||||
)
|
||||
)
|
||||
# since this token is unknown trezor should display "unknown token value"
|
||||
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/1"),
|
||||
nonce=0,
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
gas_limit=20,
|
||||
# unknown token address (Grzegorz Brzęczyszczykiewicz Token)
|
||||
to="0xfc6b5d6af8a13258f7cbd0d39e11b35e01a32f93",
|
||||
chain_id=1,
|
||||
# value needs to be 0, token value is set in the contract (data)
|
||||
value=0,
|
||||
data=data,
|
||||
)
|
||||
|
||||
assert sig_v == 1
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "e631b56bcc596844cb8686b2046e36cf33634aa396e7e1ea94a97aac02c18bda"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "399bff8752539176c4b2f1d5d2a8f6029f79841d28802149ab339a033ffe4c1f"
|
||||
)
|
||||
|
||||
|
||||
def test_ethereum_signtx_large_chainid(client):
|
||||
with client:
|
||||
|
||||
sig_v, sig_r, sig_s = ethereum.sign_tx_eip1559(
|
||||
client,
|
||||
n=parse_path("44'/60'/0'/0/100"),
|
||||
nonce=0,
|
||||
gas_limit=20,
|
||||
to=TO_ADDR,
|
||||
chain_id=3125659152, # Pirl chain id, doesn't support EIP1559 at this time, but chosen for large chain id
|
||||
value=10,
|
||||
max_gas_fee=20,
|
||||
max_priority_fee=1,
|
||||
)
|
||||
|
||||
assert sig_v == 0
|
||||
assert (
|
||||
sig_r.hex()
|
||||
== "07f8c967227c5a190cb90525c3387691a426fe61f8e0503274280724060ea95c"
|
||||
)
|
||||
assert (
|
||||
sig_s.hex()
|
||||
== "0bf83eaf74e24aa9146b23e06f9edec6e25acb81d3830e8d146b9e7b6923ad1e"
|
||||
)
|
Loading…
Reference in new issue