cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
eth_json.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#include "eth_json.h"
12
13#include <string.h>
14
15#include "CW_Utils.h" /* hardened memory primitives (CODING_RULES ยง1.4) */
16#include "cJSON.h"
17
18bool eth_json_result_string(const char *resp, char *out, size_t out_size)
19{
20 bool ok = false;
21 cJSON *root = cJSON_Parse(resp);
22 if (root == NULL) {
23 return false;
24 }
25 const cJSON *result = cJSON_GetObjectItemCaseSensitive(root, "result");
26 if (cJSON_IsString(result) && (result->valuestring != NULL)) {
27 size_t len = strlen(result->valuestring);
28 if ((len + 1U) <= out_size) {
29 ok = CW_Utils::safe_memcpy(
30 reinterpret_cast<uint8_t *>(out), out_size,
31 reinterpret_cast<const uint8_t *>(result->valuestring),
32 len + 1U);
33 }
34 }
35 cJSON_Delete(root);
36 return ok;
37}
38
39bool eth_json_error_message(const char *resp, char *out, size_t out_size)
40{
41 if ((out == NULL) || (out_size == 0U)) { return false; }
42
43 bool ok = false;
44 cJSON *root = cJSON_Parse(resp);
45 if (root == NULL) {
46 return false;
47 }
48 const cJSON *err = cJSON_GetObjectItemCaseSensitive(root, "error");
49 if (cJSON_IsObject(err)) {
50 const cJSON *msg = cJSON_GetObjectItemCaseSensitive(err, "message");
51 if (cJSON_IsString(msg) && (msg->valuestring != NULL) &&
52 (msg->valuestring[0] != '\0')) {
53 /* Truncate, unlike result_string: that one carries a tx hash, where
54 * a short copy would be a different hash and must be refused. This
55 * one carries prose for a human, and a clipped sentence is still
56 * worth more than the generic message it replaces. */
57 size_t len = strlen(msg->valuestring);
58 if (len > (out_size - 1U)) { len = out_size - 1U; }
59 ok = CW_Utils::safe_memcpy(
60 reinterpret_cast<uint8_t *>(out), out_size,
61 reinterpret_cast<const uint8_t *>(msg->valuestring), len);
62 if (ok) { out[len] = '\0'; }
63 }
64 }
65 cJSON_Delete(root);
66 return ok;
67}
68
70{
72 cJSON *root = cJSON_Parse(resp);
73 if (root == NULL) {
75 }
76 const cJSON *result = cJSON_GetObjectItemCaseSensitive(root, "result");
77 if (cJSON_IsNull(result)) {
78 verdict = ETH_JSON_RECEIPT_PENDING; /* not mined yet */
79 } else if (cJSON_IsObject(result)) {
80 const cJSON *status =
81 cJSON_GetObjectItemCaseSensitive(result, "status");
82 if (cJSON_IsString(status) && (status->valuestring != NULL)) {
83 verdict = (strcmp(status->valuestring, "0x1") == 0)
86 }
87 }
88 cJSON_Delete(root);
89 return verdict;
90}
91
92/* keccak256("Transfer(address,address,uint256)") */
93static const char TRANSFER_TOPIC[] =
94 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
95
96static char lc(char c)
97{
98 return ((c >= 'A') && (c <= 'Z')) ? static_cast<char>(c + ('a' - 'A')) : c;
99}
100
102static bool str_is(const cJSON *j, const char *want)
103{
104 if (!cJSON_IsString(j) || (j->valuestring == NULL)) { return false; }
105 const char *v = j->valuestring;
106 size_t i = 0U;
107 for (; (v[i] != '\0') && (want[i] != '\0'); i++) {
108 if (lc(v[i]) != lc(want[i])) { return false; }
109 }
110 return (v[i] == '\0') && (want[i] == '\0');
111}
112
114static void addr_hex(const uint8_t a[20], bool word, char *out)
115{
116 static const char H[] = "0123456789abcdef";
117 size_t p = 0U;
118 out[p++] = '0';
119 out[p++] = 'x';
120 if (word) { for (size_t i = 0U; i < 24U; i++) { out[p++] = '0'; } }
121 for (size_t i = 0U; i < 20U; i++) {
122 out[p++] = H[a[i] >> 4];
123 out[p++] = H[a[i] & 0x0FU];
124 }
125 out[p] = '\0';
126}
127
129static void word_hex(uint64_t v, char *out)
130{
131 static const char H[] = "0123456789abcdef";
132 size_t p = 0U;
133 out[p++] = '0';
134 out[p++] = 'x';
135 for (size_t i = 0U; i < 48U; i++) { out[p++] = '0'; }
136 for (int sh = 60; sh >= 0; sh -= 4) {
137 out[p++] = H[(v >> sh) & 0x0FULL];
138 }
139 out[p] = '\0';
140}
141
143static bool has_transfer(const cJSON *logs, const char *token,
144 const char *payee_word, const char *amount_word)
145{
146 const cJSON *log = NULL;
147 cJSON_ArrayForEach(log, logs) {
148 const cJSON *topics = cJSON_GetObjectItemCaseSensitive(log, "topics");
149 if (!str_is(cJSON_GetObjectItemCaseSensitive(log, "address"), token) ||
150 !cJSON_IsArray(topics) || (cJSON_GetArraySize(topics) != 3)) {
151 continue;
152 }
153 if (str_is(cJSON_GetArrayItem(topics, 0), TRANSFER_TOPIC) &&
154 str_is(cJSON_GetArrayItem(topics, 2), payee_word) &&
155 str_is(cJSON_GetObjectItemCaseSensitive(log, "data"), amount_word)) {
156 return true;
157 }
158 }
159 return false;
160}
161
163 const eth_receipt_expect_t *want)
164{
165 if ((want == NULL) || (want->tx_hash == NULL) || (want->to == NULL)) {
167 }
168 const eth_json_receipt_t status = eth_json_receipt_status(resp);
169 if ((status != ETH_JSON_RECEIPT_SUCCESS) &&
170 (status != ETH_JSON_RECEIPT_REVERTED)) {
171 return status; /* pending, or not a receipt at all */
172 }
173
174 cJSON *root = cJSON_Parse(resp);
175 if (root == NULL) { return ETH_JSON_RECEIPT_ERROR; }
176 const cJSON *r = cJSON_GetObjectItemCaseSensitive(root, "result");
177
178 char to_hex[43];
179 addr_hex(want->to, false, to_hex);
180 bool match = str_is(cJSON_GetObjectItemCaseSensitive(r, "transactionHash"),
181 want->tx_hash) &&
182 str_is(cJSON_GetObjectItemCaseSensitive(r, "to"), to_hex);
183
184 /* A reverted token call moved nothing, so there is no log to look for โ€”
185 * but it still has to be OUR transaction before its verdict is believed. */
186 if (match && (status == ETH_JSON_RECEIPT_SUCCESS) && (want->payee != NULL)) {
187 char payee_word[67];
188 char amount_word[67];
189 addr_hex(want->payee, true, payee_word);
190 word_hex(want->amount, amount_word);
191 match = has_transfer(cJSON_GetObjectItemCaseSensitive(r, "logs"),
192 to_hex, payee_word, amount_word);
193 }
194 cJSON_Delete(root);
195 return match ? status : ETH_JSON_RECEIPT_MISMATCH;
196}
bool eth_json_error_message(const char *resp, char *out, size_t out_size)
Extract the JSON-RPC error.message from a response body.
Definition eth_json.cpp:39
static char lc(char c)
Definition eth_json.cpp:96
static const char TRANSFER_TOPIC[]
Definition eth_json.cpp:93
eth_json_receipt_t eth_json_receipt_status(const char *resp)
Classify an eth_getTransactionReceipt response body.
Definition eth_json.cpp:69
static bool str_is(const cJSON *j, const char *want)
true if a JSON string equals want, ignoring hex case.
Definition eth_json.cpp:102
static bool has_transfer(const cJSON *logs, const char *token, const char *payee_word, const char *amount_word)
true if logs holds a Transfer(*, payee, amount) from token.
Definition eth_json.cpp:143
static void addr_hex(const uint8_t a[20], bool word, char *out)
"0x" + the 20 bytes as hex, or left-padded to a 32-byte word.
Definition eth_json.cpp:114
bool eth_json_result_string(const char *resp, char *out, size_t out_size)
Extract the top-level "result" string from a JSON-RPC response.
Definition eth_json.cpp:18
static void word_hex(uint64_t v, char *out)
"0x" + v as a 32-byte big-endian word.
Definition eth_json.cpp:129
eth_json_receipt_t eth_json_receipt_check(const char *resp, const eth_receipt_expect_t *want)
Classify a receipt AND check it is the payment that was asked for.
Definition eth_json.cpp:162
JSON-RPC response parsing helpers โ€” kept in their own unit (cJSON + CW_Utils only,...
eth_json_receipt_t
Verdict for an eth_getTransactionReceipt response body.
Definition eth_json.h:26
@ ETH_JSON_RECEIPT_REVERTED
Definition eth_json.h:32
@ ETH_JSON_RECEIPT_PENDING
Definition eth_json.h:30
@ ETH_JSON_RECEIPT_SUCCESS
Definition eth_json.h:31
@ ETH_JSON_RECEIPT_ERROR
Definition eth_json.h:27
@ ETH_JSON_RECEIPT_MISMATCH
Definition eth_json.h:33
static esp_err_t ok(httpd_req_t *req, const char *msg)
200 with a plain-text message.
What a receipt must show for the payment to count โ€” see eth_json_receipt_check.
Definition eth_json.h:40
const uint8_t * to
Definition eth_json.h:43
const uint8_t * payee
Definition eth_json.h:45
const char * tx_hash
Definition eth_json.h:41