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

Implementation of EIP 7702 - phase 1 #739

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
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
23 changes: 20 additions & 3 deletions client/src/ledger_app_clients/ethereum/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,24 @@ def eip712_filtering_raw(self, name: str, sig: bytes, discarded: bool):
def sign(self,
bip32_path: str,
tx_params: dict,
tx_raw : bytes = None, # Introduced for 7702 until web3.py supports authorization lists
mode: SignMode = SignMode.BASIC):
tx = Web3().eth.account.create().sign_transaction(tx_params).rawTransaction
if tx_raw is not None:
tx = tx_raw
else:
tx = Web3().eth.account.create().sign_transaction(tx_params).rawTransaction
prefix = bytes()
suffix = []
if tx[0] in [0x01, 0x02]:
if tx[0] in [0x01, 0x02, 0x04]:
prefix = tx[:1]
tx = tx[len(prefix):]
else: # legacy
if "chainId" in tx_params:
suffix = [int(tx_params["chainId"]), bytes(), bytes()]
decoded = rlp.decode(tx)[:-3] # remove already computed signature
if tx_raw is None:
decoded = rlp.decode(tx)[:-3] # remove already computed signature
else:
decoded = rlp.decode(tx)
tx = prefix + rlp.encode(decoded + suffix)
chunks = self._cmd_builder.sign(bip32_path, tx, mode)
for chunk in chunks[:-1]:
Expand Down Expand Up @@ -586,3 +593,13 @@ def provide_transaction_info(self, payload: bytes) -> RAPDU:
for chunk in chunks[:-1]:
self._exchange(chunk)
return self._exchange(chunks[-1])

def sign_eip7702_authorization(self,
bip32_path: str,
delegate: bytes,
nonce: int,
chain_id: Optional[int] = None) -> RAPDU:
return self._exchange_async(self._cmd_builder.sign_eip7702_authorization(bip32_path,
delegate,
nonce,
chain_id))
25 changes: 24 additions & 1 deletion client/src/ledger_app_clients/ethereum/command_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp.adoc

import struct
import math
from enum import IntEnum
from typing import List, Optional
from ragger.bip import pack_derivation_path
Expand All @@ -28,7 +29,7 @@ class InsType(IntEnum):
PROVIDE_ENUM_VALUE = 0x24
PROVIDE_TRANSACTION_INFO = 0x26
PROVIDE_NETWORK_INFORMATION = 0x30

SIGN_EIP7702_AUTHORIZATION = 0x32

class P1Type(IntEnum):
COMPLETE_SEND = 0x00
Expand Down Expand Up @@ -60,6 +61,12 @@ class P2Type(IntEnum):
class CommandBuilder:
_CLA: int = 0xE0

def _intToBytes(self, i: int) -> bytes:
if i == 0:
return b"\x00"
return i.to_bytes(math.ceil(i.bit_length() / 8), 'big')


def _serialize(self,
ins: InsType,
p1: int,
Expand Down Expand Up @@ -430,6 +437,21 @@ def provide_network_information(self,
p1 = P1Type.FOLLOWING_CHUNK
return chunks

def sign_eip7702_authorization(self, bip32_path: str, delegate:bytes, nonce: int, chain_id: Optional[int]) -> bytes:
data = pack_derivation_path(bip32_path)
data += delegate
if chain_id is None:
chain_id = 0
tmp = self._intToBytes(chain_id)
data += struct.pack(">B", len(tmp)) + tmp
tmp = self._intToBytes(nonce)
data += struct.pack(">B", len(tmp)) + tmp
return self._serialize(InsType.SIGN_EIP7702_AUTHORIZATION,
0x00,
0x00,
data)


def common_tlv_serialize(self, tlv_payload: bytes, ins: InsType) -> list[bytes]:
chunks = list()
payload = struct.pack(">H", len(tlv_payload))
Expand All @@ -449,3 +471,4 @@ def provide_enum_value(self, tlv_payload: bytes) -> list[bytes]:

def provide_transaction_info(self, tlv_payload: bytes) -> list[bytes]:
return self.common_tlv_serialize(tlv_payload, InsType.PROVIDE_TRANSACTION_INFO)

2 changes: 2 additions & 0 deletions client/src/ledger_app_clients/ethereum/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class SettingID(Enum):
VERBOSE_ENS = auto()
NONCE = auto()
VERBOSE_EIP712 = auto()
DISABLE_EIP7702_WHITELIST = auto()
DEBUG_DATA = auto()


Expand All @@ -24,6 +25,7 @@ def get_device_settings(firmware: Firmware) -> list[SettingID]:
SettingID.VERBOSE_ENS,
SettingID.NONCE,
SettingID.VERBOSE_EIP712,
SettingID.DISABLE_EIP7702_WHITELIST,
SettingID.DEBUG_DATA,
]

Expand Down
14 changes: 10 additions & 4 deletions client/src/ledger_app_clients/ethereum/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ def recover_message(msg, vrs: tuple) -> bytes:
return bytes.fromhex(addr[2:])


def recover_transaction(tx_params, vrs: tuple) -> bytes:
raw_tx = Account.create().sign_transaction(tx_params).rawTransaction
def recover_transaction(tx_params, vrs: tuple, raw_tx_param:bytes = None) -> bytes:
if raw_tx_param is None:
raw_tx = Account.create().sign_transaction(tx_params).rawTransaction
else:
raw_tx = raw_tx_param
prefix = bytes()
if raw_tx[0] in [0x01, 0x02]:
if raw_tx[0] in [0x01, 0x02, 0x04]:
prefix = raw_tx[:1]
raw_tx = raw_tx[len(prefix):]
else:
Expand Down Expand Up @@ -58,6 +61,9 @@ def recover_transaction(tx_params, vrs: tuple) -> bytes:
# Pre EIP-155 TX
assert False
decoded = rlp.decode(raw_tx)
reencoded = rlp.encode(decoded[:-3] + list(normalize_vrs(vrs)))
if raw_tx_param is None:
reencoded = rlp.encode(decoded[:-3] + list(normalize_vrs(vrs)))
else:
reencoded = rlp.encode(decoded + list(normalize_vrs(vrs)))
addr = Account.recover_transaction(prefix + reencoded)
return bytes.fromhex(addr[2:])
45 changes: 44 additions & 1 deletion doc/ethapp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The address can be optionally checked on the device before being returned.

#### Description

https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md
The application supports signing legacy or EIP 2718 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md) transactions for Type 2 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) and Type 4 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md)

This command signs an Ethereum transaction after having the user validate the following parameters

Expand Down Expand Up @@ -1274,6 +1274,49 @@ _Output data_
| Networks chain_id | 8
|==========================================

### SIGN EIP 7702 AUTHORIZATION

#### Description

This command computes the signature for an element of a EIP 7702 Authorization list (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md) on the given account, for the given delegate, chain ID and nonce.

The user is prompted to confirm the operation before the signature is issued.

#### Coding

_Command_

[width="80%"]
|==============================================================
| *CLA* | *INS* | *P1* | *P2* | *LC*
| E0 | 32 | 00 | 00 | 00
|==============================================================

_Input data_

[width="80%"]
|==========================================
| *Description* | *Length (byte)*
| Number of BIP 32 derivations to perform (max 10) | 1
| First derivation index (big endian) | 4
| ... | 4
| Last derivation index (big endian) | 4
| Delegate address | 20
| Length of Chain ID (max 32) | 1
| Chain ID (smallest big endian encoding) | variable
| Length of Nonce (max 8) | 1
| Nonce (smallest big endian encoding) | variable
|==========================================

_Output data_

[width="80%"]
|==============================================================================================================================
| *Description* | *Length*
| Signature parity (0 even, 1 odd) - use as is in EIP 7702 | 1
| r | 32
| s | 32
|==============================================================================================================================

## Transport protocol

Expand Down
84 changes: 84 additions & 0 deletions examples/signAuthorizationEIP7702.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python
"""
*******************************************************************************
* Ledger Ethereum App
* (c) 2016-2019 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
"""
from __future__ import print_function

from ledgerblue.comm import getDongle
from ledgerblue.commException import CommException
import argparse
import struct
import binascii
import math
import rlp

def intToBytes(i: int) -> bytes:
if i == 0:
return b"\x00"
return i.to_bytes(math.ceil(i.bit_length() / 8), 'big')

def parse_bip32_path(path):
if len(path) == 0:
return b""
result = b""
elements = path.split('/')
for pathElement in elements:
element = pathElement.split('\'')
if len(element) == 1:
result = result + struct.pack(">I", int(element[0]))
else:
result = result + struct.pack(">I", 0x80000000 | int(element[0]))
return result


parser = argparse.ArgumentParser()
parser.add_argument('--path', help="BIP 32 path to retrieve")
parser.add_argument('--chainid', help="Chain ID", type=int, required=True)
parser.add_argument('--nonce', help="Account Nonce", type=int, required=True)
parser.add_argument('--delegate', help="Delegate address", type=str, required=True)
args = parser.parse_args()

if args.path == None:
args.path = "44'/60'/0'/0/0"

data = binascii.unhexlify(args.delegate[2:])
tmp = intToBytes(args.chainid)
data += struct.pack(">B", len(tmp)) + tmp
tmp = intToBytes(args.nonce)
data += struct.pack(">B", len(tmp)) + tmp

donglePath = parse_bip32_path(args.path)
apdu = bytearray.fromhex("e0320000")
apdu += struct.pack(">B", len(donglePath) + 1 + len(data))
apdu += struct.pack(">B", len(donglePath) // 4)
apdu += donglePath + data

dongle = getDongle(True)
result = dongle.exchange(bytes(apdu))

v = result[0]
r = result[1 : 1 + 32]
s = result[1 + 32 :]

print("v = " + str(v))
print("r = " + binascii.hexlify(r).decode('utf-8'))
print("s = " + binascii.hexlify(s).decode('utf-8'))

rlpData = [ args.chainid, binascii.unhexlify(args.delegate[2:]), args.nonce, v, r, s ]
print(binascii.hexlify(rlp.encode(rlpData)).decode('utf-8'))

5 changes: 5 additions & 0 deletions makefile_conf/features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ ifneq ($(TARGET_NAME),TARGET_NANOS)
DEFINES += HAVE_DYNAMIC_NETWORKS
endif

# EIP 7702
DEFINES += HAVE_EIP7702_WHITELIST
# Test mode
DEFINES += HAVE_EIP7702_WHITELIST_TEST


# Check features incompatibilities
# --------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/apdu_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#define INS_GTP_TRANSACTION_INFO 0x26
#define INS_GTP_FIELD 0x28
#define INS_PROVIDE_NETWORK_CONFIGURATION 0x30
#define INS_SIGN_EIP7702_AUTHORIZATION 0x32
#define P1_CONFIRM 0x01
#define P1_NON_CONFIRM 0x00
#define P2_NO_CHAINCODE 0x00
Expand Down
6 changes: 6 additions & 0 deletions src/common_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ void ui_712_switch_to_sign(void);
// Generic clear-signing
bool ui_gcs(void);

// EIP-7702
void ui_sign_7702_auth(void);
#ifdef HAVE_EIP7702_WHITELIST
void ui_error_no_7702_whitelist(void);
#endif // HAVE_EIP7702_WHITELIST

#include "ui_callbacks.h"
#include <string.h>

Expand Down
5 changes: 5 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "cmd_enum_value.h"
#include "cmd_tx_info.h"
#include "cmd_field.h"
#include "commands_7702.h"

tmpCtx_t tmpCtx;
txContext_t txContext;
Expand Down Expand Up @@ -261,6 +262,10 @@ static uint16_t handleApdu(command_t *cmd, uint32_t *flags, uint32_t *tx) {
break;
#endif // HAVE_DYNAMIC_NETWORKS

case INS_SIGN_EIP7702_AUTHORIZATION:
sw = handleSignEIP7702Authorization(cmd->p1, cmd->p2, cmd->data, cmd->lc, flags, tx);
break;

default:
sw = APDU_RESPONSE_INVALID_INS;
break;
Expand Down
1 change: 1 addition & 0 deletions src/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ uint64_t get_tx_chain_id(void) {
break;
case EIP2930:
case EIP1559:
case EIP7702:
chain_id = u64_from_BE(tmpContent.txContent.chainID.value,
tmpContent.txContent.chainID.length);
break;
Expand Down
9 changes: 9 additions & 0 deletions src/shared_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ typedef struct internalStorage_t {
#ifdef HAVE_TRUSTED_NAME
bool verbose_trusted_name;
#endif // HAVE_TRUSTED_NAME
#ifdef HAVE_EIP7702_WHITELIST
bool eip7702_whitelist_disabled;
#endif // HAVE_EIP7702_WHITELIST
bool initialized;
} internalStorage_t;

Expand Down Expand Up @@ -98,11 +101,17 @@ typedef struct messageSigningContext712_t {
uint8_t messageHash[32];
} messageSigningContext712_t;

typedef struct authSigningContext7702_t {
bip32_path_t bip32;
uint8_t authHash[INT256_LENGTH];
} authSigningContext7702_t;

typedef union {
publicKeyContext_t publicKeyContext;
transactionContext_t transactionContext;
messageSigningContext_t messageSigningContext;
messageSigningContext712_t messageSigningContext712;
authSigningContext7702_t authSigningContext7702;
} tmpCtx_t;

typedef union {
Expand Down
2 changes: 2 additions & 0 deletions src/ui_callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ unsigned int io_seproxyhal_touch_privacy_cancel(void);
unsigned int address_cancel_cb(void);
unsigned int tx_ok_cb(void);
unsigned int tx_cancel_cb(void);
unsigned int auth_7702_ok_cb(void);
unsigned int auth_7702_cancel_cb(void);

uint16_t io_seproxyhal_send_status(uint16_t sw, uint32_t tx, bool reset, bool idle);
10 changes: 10 additions & 0 deletions src_bagl/common_ui.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,14 @@ unsigned int tx_cancel_cb(void) {
return io_seproxyhal_touch_tx_cancel();
}

void ui_sign_7702_auth(void) {
ux_flow_init(0, ux_auth7702_flow, NULL);
}

#ifdef HAVE_EIP7702_WHITELIST
void ui_error_no_7702_whitelist(void) {
ux_flow_init(0, ux_error_no_7702_whitelist_flow, NULL);
}
#endif // HAVE_EIP7702_WHITELIST

#endif // HAVE_BAGL
Loading