cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
eth_rlp.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
10
11/******************************************************************
12 * 1. Included files
13 ******************************************************************/
14
15#include "eth_rlp.h"
16#include "CW_Utils.h" /* bounds-checked memory primitives (CODING_RULES 1.4) */
17
18/******************************************************************
19 * 2. Constants
20 ******************************************************************/
21
22/* Maximum scratch buffer for the list content before prepending the header. */
23#define ITEMS_BUF_MAX 320U
24
25/*
26 * Worst-case bytes consumed in items[] by every non-calldata field:
27 * 5x uint64 (<=9 each = 45) + to (21) + eth_value (<=9) + calldata
28 * header (<=9) + empty access list (1) + v (<=9) + r (<=33) + s (<=33) = 160.
29 * Any calldata longer than what remains would overflow the stack buffer
30 * before the out_max check ever runs, so it is rejected up front.
31 */
32#define ITEMS_FIXED_MAX 160U
33#define CALLDATA_MAX (ITEMS_BUF_MAX - ITEMS_FIXED_MAX)
34
35/******************************************************************
36 * 3. Internal helpers
37 *
38 * NOTE (CODING_RULES 1.4): every byte copy goes through
39 * CW_Utils::safe_memcpy, which bounds-checks against the destination
40 * capacity threaded in as out_cap. Helpers return the number of bytes
41 * written, or 0 on a capacity failure (every successful encode writes at
42 * least one byte, so 0 is an unambiguous error code that callers propagate).
43 ******************************************************************/
44
55static uint8_t be_minimal(uint64_t value, uint8_t out[8])
56{
57 uint8_t tmp[8];
58 uint8_t first = 0U;
59 uint8_t i;
60
61 tmp[0] = static_cast<uint8_t>((value >> 56U) & 0xFFU);
62 tmp[1] = static_cast<uint8_t>((value >> 48U) & 0xFFU);
63 tmp[2] = static_cast<uint8_t>((value >> 40U) & 0xFFU);
64 tmp[3] = static_cast<uint8_t>((value >> 32U) & 0xFFU);
65 tmp[4] = static_cast<uint8_t>((value >> 24U) & 0xFFU);
66 tmp[5] = static_cast<uint8_t>((value >> 16U) & 0xFFU);
67 tmp[6] = static_cast<uint8_t>((value >> 8U) & 0xFFU);
68 tmp[7] = static_cast<uint8_t>(value & 0xFFU);
69
70 while ((first < 7U) && (tmp[first] == 0U)) {
71 first++;
72 }
73 uint8_t count = static_cast<uint8_t>(8U - first);
74 for (i = 0U; i < count; i++) {
75 out[i] = tmp[static_cast<size_t>(first) + i];
76 }
77 return count;
78}
79
89static size_t rlp_bytes(const uint8_t *data, size_t data_len,
90 uint8_t *out, size_t out_cap)
91{
92 size_t written = 0U;
93
94 if (data_len == 0U) {
95 if (out_cap < 1U) { return 0U; }
96 out[0] = 0x80U;
97 written = 1U;
98 } else if ((data_len == 1U) && (data[0] < 0x80U)) {
99 if (out_cap < 1U) { return 0U; }
100 out[0] = data[0];
101 written = 1U;
102 } else if (data_len <= 55U) {
103 if (out_cap < 1U) { return 0U; }
104 out[0] = static_cast<uint8_t>(0x80U + data_len);
105 if (!CW_Utils::safe_memcpy(out + 1U, out_cap - 1U, data, data_len)) {
106 return 0U;
107 }
108 written = 1U + data_len;
109 } else {
110 /* Long string: 0xb7 + len(length) bytes, then length, then data. */
111 uint8_t len_bytes[8];
112 uint8_t num_lb = be_minimal(static_cast<uint64_t>(data_len), len_bytes);
113 if (out_cap < (static_cast<size_t>(num_lb) + 1U)) { return 0U; }
114 out[0] = static_cast<uint8_t>(0xB7U + num_lb);
115 if (!CW_Utils::safe_memcpy(out + 1U, out_cap - 1U, len_bytes, num_lb)) {
116 return 0U;
117 }
118 if (!CW_Utils::safe_memcpy(out + 1U + num_lb,
119 out_cap - 1U - num_lb, data, data_len)) {
120 return 0U;
121 }
122 written = 1U + num_lb + data_len;
123 }
124 return written;
125}
126
137static size_t rlp_uint64(uint64_t value, uint8_t *out, size_t out_cap)
138{
139 if (value == 0U) {
140 if (out_cap < 1U) { return 0U; }
141 out[0] = 0x80U;
142 return 1U;
143 }
144 uint8_t be[8];
145 uint8_t be_len = be_minimal(value, be);
146 return rlp_bytes(be, be_len, out, out_cap);
147}
148
159static size_t rlp_int256(const uint8_t data[32], uint8_t *out, size_t out_cap)
160{
161 size_t first = 0U;
162 while ((first < 31U) && (data[first] == 0U)) {
163 first++;
164 }
165 if ((first == 31U) && (data[31] == 0U)) {
166 /* Value is zero */
167 if (out_cap < 1U) { return 0U; }
168 out[0] = 0x80U;
169 return 1U;
170 }
171 return rlp_bytes(data + first, 32U - first, out, out_cap);
172}
173
182static size_t rlp_list_header(size_t content_len, uint8_t *out, size_t out_cap)
183{
184 if (content_len <= 55U) {
185 if (out_cap < 1U) { return 0U; }
186 out[0] = static_cast<uint8_t>(0xC0U + content_len);
187 return 1U;
188 }
189 uint8_t len_bytes[8];
190 uint8_t num_lb = be_minimal(static_cast<uint64_t>(content_len), len_bytes);
191 if (out_cap < (static_cast<size_t>(num_lb) + 1U)) { return 0U; }
192 out[0] = static_cast<uint8_t>(0xF7U + num_lb);
193 if (!CW_Utils::safe_memcpy(out + 1U, out_cap - 1U, len_bytes, num_lb)) {
194 return 0U;
195 }
196 return 1U + num_lb;
197}
198
199/******************************************************************
200 * 4. Shared field encoder
201 ******************************************************************/
202
215static size_t encode_common_fields(const eth_tx_t *tx,
216 uint8_t *items, size_t items_cap)
217{
218 if (tx->calldata_len > CALLDATA_MAX) {
219 return 0U;
220 }
221 size_t n = 0U;
222 size_t w;
223
224 w = rlp_uint64(tx->chain_id, items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
225 w = rlp_uint64(tx->nonce, items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
226 w = rlp_uint64(tx->max_priority_fee, items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
227 w = rlp_uint64(tx->max_fee, items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
228 w = rlp_uint64(tx->gas_limit, items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
229 w = rlp_bytes(tx->to, 20U, items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
230 w = rlp_uint64(tx->eth_value, items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
231 w = rlp_bytes(tx->calldata, tx->calldata_len,
232 items + n, items_cap - n); if (w == 0U) { return 0U; } n += w;
233
234 if ((items_cap - n) < 1U) { return 0U; }
235 items[n] = 0xC0U; /* empty access list */
236 n += 1U;
237 return n;
238}
239
240/******************************************************************
241 * 5. Public API
242 ******************************************************************/
243
244size_t eth_rlp_encode_unsigned(const eth_tx_t *tx, uint8_t *out, size_t out_max)
245{
246 uint8_t items[ITEMS_BUF_MAX];
247 size_t items_len = encode_common_fields(tx, items, sizeof(items));
248 if (items_len == 0U) { return 0U; } /* calldata too large */
249
250 uint8_t hdr[10];
251 size_t hdr_len = rlp_list_header(items_len, hdr, sizeof(hdr));
252 if (hdr_len == 0U) { return 0U; }
253
254 size_t total = 1U + hdr_len + items_len; /* 0x02 prefix + list */
255 if (total > out_max) { return 0U; }
256
257 out[0] = 0x02U; /* EIP-1559 type prefix */
258 if (!CW_Utils::safe_memcpy(out + 1U, out_max - 1U, hdr, hdr_len)) {
259 return 0U;
260 }
261 if (!CW_Utils::safe_memcpy(out + 1U + hdr_len,
262 out_max - 1U - hdr_len, items, items_len)) {
263 return 0U;
264 }
265 return total;
266}
267
268size_t eth_rlp_encode_signed(const eth_tx_t *tx, uint8_t v,
269 const uint8_t r[32], const uint8_t s[32],
270 uint8_t *out, size_t out_max)
271{
272 uint8_t items[ITEMS_BUF_MAX];
273 size_t items_len = encode_common_fields(tx, items, sizeof(items));
274 if (items_len == 0U) { return 0U; } /* calldata too large */
275
276 size_t w;
277 w = rlp_uint64(static_cast<uint64_t>(v),
278 items + items_len, sizeof(items) - items_len);
279 if (w == 0U) { return 0U; } items_len += w;
280 w = rlp_int256(r, items + items_len, sizeof(items) - items_len);
281 if (w == 0U) { return 0U; } items_len += w;
282 w = rlp_int256(s, items + items_len, sizeof(items) - items_len);
283 if (w == 0U) { return 0U; } items_len += w;
284
285 uint8_t hdr[10];
286 size_t hdr_len = rlp_list_header(items_len, hdr, sizeof(hdr));
287 if (hdr_len == 0U) { return 0U; }
288
289 size_t total = 1U + hdr_len + items_len;
290 if (total > out_max) { return 0U; }
291
292 out[0] = 0x02U;
293 if (!CW_Utils::safe_memcpy(out + 1U, out_max - 1U, hdr, hdr_len)) {
294 return 0U;
295 }
296 if (!CW_Utils::safe_memcpy(out + 1U + hdr_len,
297 out_max - 1U - hdr_len, items, items_len)) {
298 return 0U;
299 }
300 return total;
301}
static size_t rlp_bytes(const uint8_t *data, size_t data_len, uint8_t *out, size_t out_cap)
RLP-encode a byte string.
Definition eth_rlp.cpp:89
size_t eth_rlp_encode_signed(const eth_tx_t *tx, uint8_t v, const uint8_t r[32], const uint8_t s[32], uint8_t *out, size_t out_max)
Encode a signed EIP-1559 transaction: 0x02 || RLP([..., v, r, s]).
Definition eth_rlp.cpp:268
static uint8_t be_minimal(uint64_t value, uint8_t out[8])
Write value as a minimal big-endian byte sequence.
Definition eth_rlp.cpp:55
static size_t encode_common_fields(const eth_tx_t *tx, uint8_t *items, size_t items_cap)
Encode the nine common EIP-1559 fields into the items buffer.
Definition eth_rlp.cpp:215
static size_t rlp_uint64(uint64_t value, uint8_t *out, size_t out_cap)
RLP-encode a non-negative integer.
Definition eth_rlp.cpp:137
#define ITEMS_BUF_MAX
Definition eth_rlp.cpp:23
static size_t rlp_int256(const uint8_t data[32], uint8_t *out, size_t out_cap)
RLP-encode a 32-byte big-endian integer (e.g. ECDSA r or s).
Definition eth_rlp.cpp:159
size_t eth_rlp_encode_unsigned(const eth_tx_t *tx, uint8_t *out, size_t out_max)
Encode an unsigned EIP-1559 transaction: 0x02 || RLP([chainId, nonce, ...]).
Definition eth_rlp.cpp:244
#define CALLDATA_MAX
Definition eth_rlp.cpp:33
static size_t rlp_list_header(size_t content_len, uint8_t *out, size_t out_cap)
Write the RLP list header for a list of content_len bytes.
Definition eth_rlp.cpp:182
Minimal RLP encoder for EIP-1559 (type 2) Ethereum transactions.
EIP-1559 (type 2) transaction parameters.
Definition eth_rlp.h:31
size_t calldata_len
Definition eth_rlp.h:41
uint64_t gas_limit
Definition eth_rlp.h:36
uint64_t eth_value
Definition eth_rlp.h:38
const uint8_t * calldata
Definition eth_rlp.h:40
uint64_t max_fee
Definition eth_rlp.h:35
uint64_t max_priority_fee
Definition eth_rlp.h:34
uint64_t chain_id
Definition eth_rlp.h:32
uint8_t to[20]
Definition eth_rlp.h:37
uint64_t nonce
Definition eth_rlp.h:33