cryptnox-pos
1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Toggle main menu visibility
Loading...
Searching...
No Matches
eth_addr.cpp
Go to the documentation of this file.
1
/*
2
* SPDX-License-Identifier: LGPL-3.0-or-later
3
* Copyright (c) 2026 Cryptnox SA
4
*/
5
11
12
#include "
eth_addr.h
"
13
14
#include <string.h>
15
#include <stdbool.h>
16
17
#include "
keccak256.h
"
18
#include "CW_Utils.h"
/* SDK hardened primitives: secure_wipe */
19
26
static
int
hex_nibble_val
(
char
c)
27
{
28
if
((c >=
'0'
) && (c <=
'9'
)) {
return
c -
'0'
; }
29
if
((c >=
'a'
) && (c <=
'f'
)) {
return
(c -
'a'
) + 10; }
30
if
((c >=
'A'
) && (c <=
'F'
)) {
return
(c -
'A'
) + 10; }
31
return
-1;
32
}
33
34
bool
eth_addr_parse
(
const
char
*hex, uint8_t out[
ETH_ADDR_LEN
])
35
{
36
const
char
*p = hex;
37
if
((p[0] ==
'0'
) && ((p[1] ==
'x'
) || (p[1] ==
'X'
))) {
38
p += 2;
39
}
40
if
(strlen(p) !=
ETH_ADDR_HEX_LEN
) {
41
return
false
;
42
}
43
44
CW_Utils::secure_wipe(out,
ETH_ADDR_LEN
);
45
46
/* Single pass: validate every nibble, decode the bytes, lower-case a copy
47
* for the checksum hash, and note whether the source is mixed-case. */
48
char
lower[
ETH_ADDR_HEX_LEN
];
49
bool
has_upper =
false
;
50
bool
has_lower =
false
;
51
size_t
i;
52
for
(i = 0U; i <
ETH_ADDR_HEX_LEN
; i++) {
53
char
c = p[i];
54
int
v =
hex_nibble_val
(c);
55
if
(v < 0) {
56
return
false
;
57
}
58
if
((c >=
'a'
) && (c <=
'f'
)) {
59
has_lower =
true
;
60
lower[i] = c;
61
}
else
if
((c >=
'A'
) && (c <=
'F'
)) {
62
has_upper =
true
;
63
lower[i] =
static_cast<
char
>
((c -
'A'
) +
'a'
);
64
}
else
{
65
lower[i] = c;
/* digit — case-neutral */
66
}
67
if
((i & 1U) == 0U) {
68
out[i / 2U] =
static_cast<
uint8_t
>
(
static_cast<
uint8_t
>
(v) << 4U);
69
}
else
{
70
out[i / 2U] |=
static_cast<
uint8_t
>
(v);
71
}
72
}
73
74
/* The zero address is nobody's: a transfer to it burns the funds, and an
75
* all-zero string is what a blank or wiped setting decodes to. It is never
76
* a payee or a contract this terminal should act on. */
77
uint8_t any = 0U;
78
for
(i = 0U; i <
ETH_ADDR_LEN
; i++) { any |= out[i]; }
79
if
(any == 0U) {
80
return
false
;
81
}
82
83
/* EIP-55: a mixed-case address carries a checksum, so verify it; an
84
* all-lower or all-upper address is treated as un-checksummed and accepted
85
* (standard lenient behaviour). A checksummed ADDR_TO thus rejects any
86
* single-nibble corruption or typo of the source string. */
87
if
(has_upper && has_lower) {
88
uint8_t h[32];
89
keccak256
(
reinterpret_cast<
const
uint8_t *
>
(lower),
ETH_ADDR_HEX_LEN
, h);
90
for
(i = 0U; i <
ETH_ADDR_HEX_LEN
; i++) {
91
char
c = p[i];
92
bool
is_alpha = ((c >=
'a'
) && (c <=
'f'
)) ||
93
((c >=
'A'
) && (c <=
'F'
));
94
if
(!is_alpha) {
95
continue
;
/* digits have no case to check */
96
}
97
uint8_t nib = ((i & 1U) == 0U) ?
static_cast<
uint8_t
>
(h[i / 2U] >> 4U)
98
:
static_cast<
uint8_t
>
(h[i / 2U] & 0x0FU);
99
bool
want_upper = (nib >= 8U);
100
bool
is_upper = ((c >=
'A'
) && (c <=
'F'
));
101
if
(want_upper != is_upper) {
102
return
false
;
103
}
104
}
105
}
106
return
true
;
107
}
108
109
bool
eth_addr_format
(
const
uint8_t addr[
ETH_ADDR_LEN
],
char
*out,
size_t
n)
110
{
111
/* "0x" + 40 hex + NUL */
112
if
((out == NULL) || (n < (
ETH_ADDR_HEX_LEN
+ 3U))) {
113
if
((out != NULL) && (n > 0U)) { out[0] =
'\0'
; }
114
return
false
;
115
}
116
117
static
const
char
LOWER[] =
"0123456789abcdef"
;
118
119
/* Lower-case hex first: EIP-55 hashes exactly that string, so it has to
120
* exist before any character can be given its case. */
121
char
lower[
ETH_ADDR_HEX_LEN
];
122
size_t
i;
123
for
(i = 0U; i <
ETH_ADDR_LEN
; i++) {
124
lower[i * 2U] = LOWER[(addr[i] >> 4U) & 0x0FU];
125
lower[(i * 2U) + 1U] = LOWER[addr[i] & 0x0FU];
126
}
127
128
uint8_t h[32];
129
keccak256
(
reinterpret_cast<
const
uint8_t *
>
(lower),
ETH_ADDR_HEX_LEN
, h);
130
131
out[0] =
'0'
;
132
out[1] =
'x'
;
133
for
(i = 0U; i <
ETH_ADDR_HEX_LEN
; i++) {
134
char
c = lower[i];
135
if
((c >=
'a'
) && (c <=
'f'
)) {
136
const
uint8_t nib = ((i & 1U) == 0U)
137
?
static_cast<
uint8_t
>
(h[i / 2U] >> 4U)
138
:
static_cast<
uint8_t
>
(h[i / 2U] & 0x0FU);
139
if
(nib >= 8U) { c =
static_cast<
char
>
((c -
'a'
) +
'A'
); }
140
}
141
out[i + 2U] = c;
142
}
143
out[
ETH_ADDR_HEX_LEN
+ 2U] =
'\0'
;
144
return
true
;
145
}
hex_nibble_val
static int hex_nibble_val(char c)
Decode a single ASCII hex digit.
Definition
eth_addr.cpp:26
eth_addr_parse
bool eth_addr_parse(const char *hex, uint8_t out[ETH_ADDR_LEN])
Parse a 20-byte Ethereum address from a hex string.
Definition
eth_addr.cpp:34
eth_addr_format
bool eth_addr_format(const uint8_t addr[ETH_ADDR_LEN], char *out, size_t n)
Render a 20-byte address as an EIP-55 mixed-case "0x..." string.
Definition
eth_addr.cpp:109
eth_addr.h
Hex Ethereum-address parsing — a pure, dependency-free unit so it can be unit-tested and fuzzed on th...
ETH_ADDR_HEX_LEN
#define ETH_ADDR_HEX_LEN
Length of the same address in hex characters (no 0x prefix).
Definition
eth_addr.h:34
ETH_ADDR_LEN
#define ETH_ADDR_LEN
Length of a raw (binary) Ethereum address, in bytes.
Definition
eth_addr.h:25
keccak256
void keccak256(const uint8_t *input, size_t length, uint8_t digest[32])
Compute the Keccak-256 digest of a buffer.
Definition
keccak256.cpp:120
keccak256.h
Original Keccak-256 digest as used by Ethereum.
main
eth_addr.cpp
Generated by
1.17.0