Skip to content

Commit be53fee

Browse files
Merge pull request #772 from LedgerHQ/feat/apa/7702_changes
EIP-7702 changes
2 parents c513790 + 3f9583f commit be53fee

File tree

14 files changed

+179
-182
lines changed

14 files changed

+179
-182
lines changed

.github/workflows/build_and_functional_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
3333
with:
3434
upload_app_binaries_artifact: "ragger_elfs"
35-
flags: "CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1"
35+
flags: "CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1 EIP7702_TEST_WHITELIST=1"
3636

3737
ragger_tests:
3838
name: Run ragger tests using the reusable workflow

client/src/ledger_app_clients/ethereum/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,8 @@ def provide_proxy_info(self, payload: bytes) -> RAPDU:
680680
self._exchange(chunk)
681681
return self._exchange(chunks[-1])
682682

683-
def sign_eip7702_authorization(self, auth_params: TxAuth7702) -> RAPDU:
684-
chunks = self._cmd_builder.sign_eip7702_authorization(auth_params.serialize())
683+
def sign_eip7702_authorization(self, bip32_path: str, auth_params: TxAuth7702) -> RAPDU:
684+
chunks = self._cmd_builder.sign_eip7702_authorization(bip32_path, auth_params.serialize())
685685
for chunk in chunks[:-1]:
686686
self._exchange(chunk)
687687
return self._exchange_async(chunks[-1])

client/src/ledger_app_clients/ethereum/command_builder.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -426,11 +426,12 @@ def common_tlv_serialize(self,
426426
ins: InsType,
427427
tlv_payload: bytes,
428428
p1l: list[int] = [0x01, 0x00],
429-
p2l: list[int] = [0x00]) -> list[bytes]:
429+
p2l: list[int] = [0x00],
430+
payload: bytes = bytes()) -> list[bytes]:
430431
assert len(p1l) in [1, 2]
431432
assert len(p2l) in [1, 2]
432433
chunks = []
433-
payload = struct.pack(">H", len(tlv_payload))
434+
payload += struct.pack(">H", len(tlv_payload))
434435
payload += tlv_payload
435436
p1 = p1l[0]
436437
p2 = p2l[0]
@@ -463,8 +464,10 @@ def provide_network_information(self,
463464
p1 = P1Type.FOLLOWING_CHUNK
464465
return chunks
465466

466-
def sign_eip7702_authorization(self, tlv_payload: bytes) -> list[bytes]:
467-
return self.common_tlv_serialize(InsType.SIGN_EIP7702_AUTHORIZATION, tlv_payload)
467+
def sign_eip7702_authorization(self, bip32_path: str, tlv_payload: bytes) -> list[bytes]:
468+
return self.common_tlv_serialize(InsType.SIGN_EIP7702_AUTHORIZATION,
469+
tlv_payload,
470+
payload=pack_derivation_path(bip32_path))
468471

469472
def provide_enum_value(self, tlv_payload: bytes) -> list[bytes]:
470473
return self.common_tlv_serialize(InsType.PROVIDE_ENUM_VALUE, tlv_payload)
Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
11
from typing import Optional
22
from enum import IntEnum
3-
4-
from bip_utils import Bip32Utils
5-
63
from .tlv import TlvSerializable
74

85

96
class FieldTag(IntEnum):
107
STRUCT_VERSION = 0x00
11-
DERIVATION_IDX = 0x01
12-
DELEGATE_ADDR = 0x02
13-
CHAIN_ID = 0x03
14-
NONCE = 0x04
8+
DELEGATE_ADDR = 0x01
9+
CHAIN_ID = 0x02
10+
NONCE = 0x03
1511

1612

1713
class TxAuth7702(TlvSerializable):
18-
bip32_path: str
1914
delegate: bytes
2015
nonce: int
2116
chain_id: int
2217

2318
def __init__(self,
24-
bip32_path: str,
2519
delegate: bytes,
2620
nonce: int,
2721
chain_id: Optional[int]) -> None:
28-
self.bip32_path = bip32_path
2922
self.delegate = delegate
3023
self.nonce = nonce
3124
if chain_id is None:
@@ -35,19 +28,7 @@ def __init__(self,
3528

3629
def serialize(self) -> bytes:
3730
payload: bytes = self.serialize_field(FieldTag.STRUCT_VERSION, 1)
38-
split = self.bip32_path.split("/")
39-
if split[0] != "m":
40-
raise ValueError("Error master expected in bip32 path")
41-
for value in split[1:]:
42-
if value == "":
43-
raise ValueError(f'Error missing value in split bip 32 path list "{split}"')
44-
if value.endswith('\''):
45-
payload += self.serialize_field(FieldTag.DERIVATION_IDX,
46-
Bip32Utils.HardenIndex(int(value[:-1])).to_bytes(4, byteorder='big'))
47-
else:
48-
payload += self.serialize_field(FieldTag.DERIVATION_IDX,
49-
int(value).to_bytes(4, byteorder='big'))
5031
payload += self.serialize_field(FieldTag.DELEGATE_ADDR, self.delegate)
51-
payload += self.serialize_field(FieldTag.NONCE, self.nonce.to_bytes(8, 'big'))
52-
payload += self.serialize_field(FieldTag.CHAIN_ID, self.chain_id.to_bytes(8, 'big'))
32+
payload += self.serialize_field(FieldTag.CHAIN_ID, self.chain_id)
33+
payload += self.serialize_field(FieldTag.NONCE, self.nonce)
5334
return payload

doc/ethapp.adoc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,15 +1483,19 @@ _Input data_
14831483
[width="80%"]
14841484
|====================================================================
14851485
| *Description* | *Length (byte)*
1486+
| Number of BIP 32 derivations to perform (max 10) | 1
1487+
| First derivation index (BE) | 4
1488+
| ... | 4
1489+
| Last derivation index (BE) | 4
14861490
| struct size (BE) | 2
1487-
| link:tlv_structs.md#auth_7702[AUTH_7702 struct] | variable
1491+
| link:tlv_structs.md#auth_7702[AUTH_7702 struct] | variable
14881492
|====================================================================
14891493
14901494
##### If P1 == following chunk
14911495
14921496
[width="80%"]
14931497
|====================================================================
1494-
| *Description* | *Length (byte)*
1498+
| *Description* | *Length (byte)*
14951499
| link:tlv_structs.md#auth_7702[AUTH_7702 struct] | variable
14961500
|====================================================================
14971501

doc/tlv_structs.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ In version 1 of the protocol:
318318
| Name | Tag | Payload type | Description | Optional |
319319
|----------------|------|-----------------|---------------------------------|----------|
320320
| STRUCT_VERSION | 0x00 | uint8 | structure version (currently 1) | |
321-
| DERIVATION_IDX | 0x01 | uint32 | BIP32 derivation path item | |
322-
| DELEGATE_ADDR | 0x02 | uint8[20] | delegate address | |
323-
| CHAIN_ID | 0x03 | uint64 | Chain ID (00 for no restriction)| |
324-
| NONCE | 0x04 | uint64 | nonce | |
321+
| DELEGATE_ADDR | 0x01 | uint8[20] | delegate address | |
322+
| CHAIN_ID | 0x02 | uint64 | chain ID (0 for no restriction) | |
323+
| NONCE | 0x03 | uint64 | nonce | |
Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python3
22
"""
33
*******************************************************************************
44
* Ledger Ethereum App
@@ -20,32 +20,31 @@
2020
from __future__ import print_function
2121

2222
from ledgerblue.comm import getDongle
23-
from ledgerblue.commException import CommException
2423
import argparse
2524
import struct
2625
import binascii
27-
import math
2826
import rlp
2927

3028

31-
3229
def der_encode(value):
3330
value_bytes = value.to_bytes(max(1, (value.bit_length() + 7) // 8), 'big')
3431
if value >= 0x80:
3532
value_bytes = (0x80 | len(value_bytes)).to_bytes(1, 'big') + value_bytes
3633
return value_bytes
3734

35+
3836
def tlv_encode(tag, value):
3937
return der_encode(tag) + der_encode(len(value)) + value
4038

41-
def parse_bip32_path(path):
39+
40+
def parse_bip32_path(path: str):
4241
if len(path) == 0:
4342
return b""
4443
result = b""
4544
elements = path.split('/')
45+
result += len(elements).to_bytes(1, "big")
4646
for pathElement in elements:
4747
element = pathElement.split('\'')
48-
result = result + der_encode(0x01) + der_encode(0x04)
4948
if len(element) == 1:
5049
result = result + struct.pack(">I", int(element[0]))
5150
else:
@@ -54,23 +53,19 @@ def parse_bip32_path(path):
5453

5554

5655
parser = argparse.ArgumentParser()
57-
parser.add_argument('--path', help="BIP 32 path to retrieve")
56+
parser.add_argument('--path', help="BIP 32 path to retrieve", default="44'/60'/0'/0/0")
5857
parser.add_argument('--chainid', help="Chain ID", type=int, required=True)
5958
parser.add_argument('--nonce', help="Account Nonce", type=int, required=True)
6059
parser.add_argument('--delegate', help="Delegate address", type=str, required=True)
6160
args = parser.parse_args()
6261

63-
if args.path == None:
64-
args.path = "44'/60'/0'/0/0"
65-
6662
tmp = tlv_encode(0x00, struct.pack(">B", 0x01))
67-
tmp += parse_bip32_path(args.path)
6863
data = binascii.unhexlify(args.delegate[2:])
69-
tmp += tlv_encode(0x02, data)
70-
tmp += tlv_encode(0x03, struct.pack(">Q", args.chainid))
71-
tmp += tlv_encode(0x04, struct.pack(">Q", args.nonce))
64+
tmp += tlv_encode(0x01, data)
65+
tmp += tlv_encode(0x02, struct.pack(">Q", args.chainid))
66+
tmp += tlv_encode(0x03, struct.pack(">Q", args.nonce))
7267

73-
tmp = struct.pack(">H", len(tmp)) + tmp
68+
tmp = parse_bip32_path(args.path) + struct.pack(">H", len(tmp)) + tmp
7469

7570
apdu = bytearray.fromhex("e0340100")
7671
apdu += struct.pack(">B", len(tmp))
@@ -80,12 +75,12 @@ def parse_bip32_path(path):
8075
result = dongle.exchange(bytes(apdu))
8176

8277
v = result[0]
83-
r = result[1 : 1 + 32]
84-
s = result[1 + 32 :]
78+
r = result[1:1 + 32]
79+
s = result[1 + 32:]
8580

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

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

ledger_app.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ sdk = "C"
44
devices = ["nanos", "nanox", "nanos+", "stax", "flex"]
55

66
[use_cases]
7-
use_test_keys = "CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1"
8-
dbg_use_test_keys = "DEBUG=1 CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1"
7+
use_test_keys = "CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1 EIP7702_TEST_WHITELIST=1"
8+
dbg_use_test_keys = "DEBUG=1 CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1 EIP7702_TEST_WHITELIST=1"
99
cal_bypass = "BYPASS_SIGNATURES=1"
1010
dbg_cal_bypass = "DEBUG=1 BYPASS_SIGNATURES=1"
1111

makefile_conf/features.mk

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,11 @@ endif
7878
ifneq ($(TARGET_NAME),TARGET_NANOS)
7979
DEFINES += HAVE_EIP7702
8080
DEFINES += HAVE_EIP7702_WHITELIST
81-
# Test mode
82-
DEFINES += HAVE_EIP7702_WHITELIST_TEST
81+
82+
EIP7702_TEST_WHITELIST ?= 0
83+
ifneq ($(EIP7702_TEST_WHITELIST),0)
84+
DEFINES += HAVE_EIP7702_WHITELIST_TEST
85+
endif
8386
endif
8487

8588
# Check features incompatibilities

src_features/signAuthorizationEIP7702/auth_7702.c

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,71 +6,63 @@
66

77
enum {
88
TAG_STRUCT_VERSION = 0x00,
9-
TAG_DERIVATION_IDX = 0x01,
10-
TAG_DELEGATE_ADDR = 0x02,
11-
TAG_CHAIN_ID = 0x03,
12-
TAG_NONCE = 0x04,
9+
TAG_DELEGATE_ADDR = 0x01,
10+
TAG_CHAIN_ID = 0x02,
11+
TAG_NONCE = 0x03,
1312
};
1413

1514
enum {
1615
BIT_VERSION,
17-
BIT_DERIVATION_IDX,
1816
BIT_DELEGATE_ADDR,
1917
BIT_CHAIN_ID,
2018
BIT_NONCE,
2119
};
2220

2321
#define STRUCT_VERSION 0x01
24-
#define MASK_ALL \
25-
(SET_BIT(BIT_VERSION) | SET_BIT(BIT_DERIVATION_IDX) | SET_BIT(BIT_DELEGATE_ADDR) | \
26-
SET_BIT(BIT_CHAIN_ID) | SET_BIT(BIT_NONCE))
22+
#define MASK_ALL \
23+
(SET_BIT(BIT_VERSION) | SET_BIT(BIT_DELEGATE_ADDR) | SET_BIT(BIT_CHAIN_ID) | SET_BIT(BIT_NONCE))
2724

2825
static bool handle_version(const s_tlv_data *data, s_auth_7702_ctx *context) {
2926
if (data->length != sizeof(uint8_t)) {
3027
return false;
3128
}
3229
context->mask_parsed |= SET_BIT(BIT_VERSION);
33-
return (data->value[0] == STRUCT_VERSION);
34-
}
35-
36-
static bool handle_derivation_idx(const s_tlv_data *data, s_auth_7702_ctx *context) {
37-
uint32_t idx;
38-
if (data->length != sizeof(uint32_t)) {
39-
return false;
40-
}
41-
if (context->auth_7702.bip32.length == MAX_BIP32_PATH) {
42-
return false;
43-
}
44-
idx = read_u32_be(data->value, 0);
45-
context->auth_7702.bip32.path[context->auth_7702.bip32.length] = idx;
46-
context->auth_7702.bip32.length++;
47-
context->mask_parsed |= SET_BIT(BIT_DERIVATION_IDX);
30+
context->version = data->value[0];
4831
return true;
4932
}
5033

5134
static bool handle_delegate_addr(const s_tlv_data *data, s_auth_7702_ctx *context) {
52-
if (data->length != sizeof(context->auth_7702.delegate)) {
35+
uint8_t buf[sizeof(context->auth_7702.delegate)];
36+
37+
if (data->length > sizeof(buf)) {
5338
return false;
5439
}
55-
memmove(context->auth_7702.delegate, data->value, sizeof(context->auth_7702.delegate));
40+
buf_shrink_expand(data->value, data->length, buf, sizeof(buf));
41+
memmove(context->auth_7702.delegate, buf, sizeof(buf));
5642
context->mask_parsed |= SET_BIT(BIT_DELEGATE_ADDR);
5743
return true;
5844
}
5945

6046
static bool handle_chain_id(const s_tlv_data *data, s_auth_7702_ctx *context) {
61-
if (data->length != sizeof(uint64_t)) {
47+
uint8_t buf[sizeof(context->auth_7702.chainId)];
48+
49+
if (data->length > sizeof(buf)) {
6250
return false;
6351
}
64-
context->auth_7702.chainId = read_u64_be(data->value, 0);
52+
buf_shrink_expand(data->value, data->length, buf, sizeof(buf));
53+
context->auth_7702.chainId = read_u64_be(buf, 0);
6554
context->mask_parsed |= SET_BIT(BIT_CHAIN_ID);
6655
return true;
6756
}
6857

6958
static bool handle_nonce(const s_tlv_data *data, s_auth_7702_ctx *context) {
70-
if (data->length != sizeof(uint64_t)) {
59+
uint8_t buf[sizeof(context->auth_7702.nonce)];
60+
61+
if (data->length > sizeof(buf)) {
7162
return false;
7263
}
73-
context->auth_7702.nonce = read_u64_be(data->value, 0);
64+
buf_shrink_expand(data->value, data->length, buf, sizeof(buf));
65+
context->auth_7702.nonce = read_u64_be(buf, 0);
7466
context->mask_parsed |= SET_BIT(BIT_NONCE);
7567
return true;
7668
}
@@ -82,9 +74,6 @@ bool handle_auth_7702_struct(const s_tlv_data *data, s_auth_7702_ctx *context) {
8274
case TAG_STRUCT_VERSION:
8375
ret = handle_version(data, context);
8476
break;
85-
case TAG_DERIVATION_IDX:
86-
ret = handle_derivation_idx(data, context);
87-
break;
8877
case TAG_DELEGATE_ADDR:
8978
ret = handle_delegate_addr(data, context);
9079
break;
@@ -101,8 +90,8 @@ bool handle_auth_7702_struct(const s_tlv_data *data, s_auth_7702_ctx *context) {
10190
return ret;
10291
}
10392

104-
bool verify_auth_7702_struct(s_auth_7702_ctx *context) {
105-
return ((context->mask_parsed & MASK_ALL) == MASK_ALL);
93+
bool verify_auth_7702_struct(const s_auth_7702_ctx *context) {
94+
return ((context->mask_parsed & MASK_ALL) == MASK_ALL) && (context->version == STRUCT_VERSION);
10695
}
10796

108-
#endif // HAVE_EIP7702
97+
#endif // HAVE_EIP7702

0 commit comments

Comments
 (0)