Skip to content

Commit 3f296e9

Browse files
committed
ur: replace esp32_bc-ur with cUR
Replace the C++20 bc-ur library (and its C shim with placement-new sizing constants) with cUR, a pure-C implementation of BCR-2020-005 (https://github.com/odudex/cUR), used as the UR transport envelope only - all payload CBOR remains TinyCBOR in bcur.c. - main/bcur.c and main/selfcheck.c moved to the cUR API: heap encoder/decoder handles, results borrowed from the decoder, fragments freed with free(). Encoder output is always uppercase. - collect_any_bcur() now replaces the decoder via qr_data->ctx on hard failure, and bcur_scan_qr() re-reads it after scanning. - selfcheck: a duplicate part is deduped before being counted, so the 'processed parts' expectation differs when the same part is presented twice. - Build with UR_ENVELOPE_ONLY to skip cUR's payload-type codecs; libjade links the same component's host static lib (bundled SHA-256, no mbedcrypto/wally dependencies). Verified: cUR test suite (incl ASan/UBSan), esp32/esp32s3 and qemu (psram + unamalgamated) builds, on-device selfcheck under qemu for both qemu configs, libjade + full python test suite.
1 parent 32590a2 commit 3f296e9

9 files changed

Lines changed: 154 additions & 144 deletions

File tree

.gitmodules

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
[submodule "components/esp32_deflate"]
1111
path = components/esp32_deflate
1212
url = https://github.com/Blockstream/esp32_deflate
13-
[submodule "components/esp32_bc-ur"]
14-
path = components/esp32_bc-ur
15-
url = https://github.com/Blockstream/esp32_bc-ur
1613
[submodule "components/k_quirc"]
1714
path = components/k_quirc
1815
url = https://github.com/odudex/k_quirc
1916
branch = master
17+
[submodule "components/cUR"]
18+
path = components/cUR
19+
url = https://github.com/odudex/cUR

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
cmake_minimum_required(VERSION 3.16)
22
if(DEFINED ESP_PLATFORM AND ESP_PLATFORM EQUAL 1)
33
set(EXTRA_COMPONENT_DIRS bootloader_components/bootloader_support)
4+
# Jade does its own payload CBOR (TinyCBOR) - only the UR transport
5+
# layer of the cUR component is needed
6+
set(UR_ENVELOPE_ONLY ON CACHE BOOL "" FORCE)
47
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
58
idf_build_set_property(DEPENDENCIES_LOCK dependencies.lock.${IDF_TARGET})
69
project(jade)

components/cUR

Submodule cUR updated from 057e419 to a660c99

components/esp32_bc-ur

Lines changed: 0 additions & 1 deletion
This file was deleted.

libjade/CMakeLists.txt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,15 @@ set(COMMON_SRC
105105
${IDF_PATH}/components/http_parser/http_parser.c
106106
)
107107

108-
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32_bc-ur
109-
${CMAKE_CURRENT_BINARY_DIR}/bcur)
110-
target_include_directories(bcur PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/include)
111-
target_link_libraries(bcur PRIVATE mbedcrypto)
108+
# Jade does its own payload CBOR (TinyCBOR) - only the UR transport
109+
# layer of the cUR component is needed
110+
set(UR_ENVELOPE_ONLY ON CACHE BOOL "" FORCE)
111+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../components/cUR
112+
${CMAKE_CURRENT_BINARY_DIR}/cur)
112113

113114
# libjade.so
114115
add_library(jade SHARED ${COMMON_SRC})
115-
target_link_libraries(jade PRIVATE m z bcur cbor_target otpauth_migrate_target mbedtls mbedcrypto mbedx509)
116+
target_link_libraries(jade PRIVATE m z ur cbor_target otpauth_migrate_target mbedtls mbedcrypto mbedx509)
116117
target_include_directories(jade PRIVATE ${COMMON_INCLUDES})
117118

118119
set_source_files_properties(libjade.c PROPERTIES COMPILE_OPTIONS
@@ -121,12 +122,12 @@ target_compile_definitions(jade PRIVATE LIBJADE_BUILD)
121122

122123
# libjade.a
123124
add_library(jade_static STATIC ${COMMON_SRC})
124-
target_link_libraries(jade_static PRIVATE m z mbedtls mbedcrypto mbedx509 bcur)
125+
target_link_libraries(jade_static PRIVATE m z mbedtls mbedcrypto mbedx509 ur)
125126
target_include_directories(jade_static PRIVATE ${COMMON_INCLUDES})
126127
target_compile_definitions(jade_static PRIVATE LIBJADE_BUILD)
127128

128129
add_executable(libjade_daemon daemon.c)
129-
target_link_libraries(libjade_daemon PRIVATE jade_static bcur cbor_target otpauth_migrate_target m z mbedtls mbedcrypto mbedx509 Threads::Threads)
130+
target_link_libraries(libjade_daemon PRIVATE jade_static ur cbor_target otpauth_migrate_target m z mbedtls mbedcrypto mbedx509 Threads::Threads)
130131
target_include_directories(libjade_daemon PRIVATE ${COMMON_INCLUDES})
131132

132133
# Assets

main/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ idf_component_register(SRC_DIRS "."
5252
"${usbdir}"
5353
"${wallydirs}"
5454
"${secpdir}"
55-
PRIV_REQUIRES assets libwally-core libsodium esp32-rotary-encoder k_quirc bootloader_support app_update nvs_flash bt autogenlang cbor esp_netif esp32_bsdiff esp32_deflate nghttp esp32_bc-ur driver mbedtls http_parser esp_hw_support efuse esp_eth esp_http_server esp_lcd usb vfs app_trace spi_flash google-otpauth-migrate ${extra_priv_requires}
55+
PRIV_REQUIRES assets libwally-core libsodium esp32-rotary-encoder k_quirc bootloader_support app_update nvs_flash bt autogenlang cbor esp_netif esp32_bsdiff esp32_deflate nghttp cUR driver mbedtls http_parser esp_hw_support efuse esp_eth esp_http_server esp_lcd usb vfs app_trace spi_flash google-otpauth-migrate ${extra_priv_requires}
5656
EMBED_FILES ${PROJECT_DIR}/pinserver_public_key.pub ${logo_files} ${qemu_display_file})
5757

5858
if(CONFIG_ETH_USE_OPENETH)

main/bcur.c

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
#include "utils/util.h"
1212

1313
#include <cbor.h>
14-
#include <cdecoder.h>
15-
#include <cencoder.h>
14+
#include <ur_decoder.h>
15+
#include <ur_encoder.h>
1616

1717
// PSBT serialisation functions
1818
bool deserialise_psbt(const uint8_t* bytes, size_t bytes_len, struct wally_psbt** psbt_out);
@@ -181,25 +181,23 @@ bool bcur_parse_bip39_wrapper(
181181

182182
// Decode bcur string
183183
bool ret = false;
184-
uint8_t decoder[URDECODER_SIZE];
185-
urcreate_placement_decoder(decoder, sizeof(decoder));
186-
if (!urreceive_part_decoder(decoder, bcur) || !uris_success_decoder(decoder)) {
184+
ur_decoder_t* const decoder = ur_decoder_new();
185+
JADE_ASSERT(decoder);
186+
if (!ur_decoder_receive_part(decoder, bcur) || !ur_decoder_is_success(decoder)) {
187187
JADE_LOGW("Unable to decode bcur bip39 string from single part");
188188
goto cleanup;
189189
}
190190

191-
// Read the result
192-
char const* type = NULL;
193-
uint8_t* result = NULL;
194-
size_t result_len = 0;
195-
urresult_ur_decoder(decoder, &result, &result_len, &type);
196-
if (!type || !result || !result_len || strcasecmp(BCUR_TYPE_CRYPTO_BIP39, type)) {
191+
// Read the result - borrowed from the decoder, freed with it
192+
const ur_result_t* const result = ur_decoder_get_result(decoder);
193+
if (!result || !result->type || !result->cbor_data || !result->cbor_len
194+
|| strcasecmp(BCUR_TYPE_CRYPTO_BIP39, result->type)) {
197195
JADE_LOGW("Unable to decode bcur bip39 string to expected type %s", BCUR_TYPE_CRYPTO_BIP39);
198196
goto cleanup;
199197
}
200198

201199
// Decode the cbor
202-
if (!bcur_parse_bip39(result, result_len, mnemonic, mnemonic_len, written)) {
200+
if (!bcur_parse_bip39(result->cbor_data, result->cbor_len, mnemonic, mnemonic_len, written)) {
203201
JADE_LOGW("Failed to parse bcur bip39 cbor message");
204202
goto cleanup;
205203
}
@@ -208,7 +206,7 @@ bool bcur_parse_bip39_wrapper(
208206
ret = true;
209207

210208
cleanup:
211-
urfree_placement_decoder(decoder);
209+
ur_decoder_free(decoder);
212210
return ret;
213211
}
214212

@@ -626,22 +624,24 @@ static bool collect_any_bcur(qr_data_t* qr_data)
626624

627625
// The scanned data looks like a bcur code or fragment, add it to the bcur decoder
628626
// and return true only when the bcur decoder says the message is complete.
629-
const bool processed_part = urreceive_part_decoder(qr_data->ctx, (const char*)qr_data->data);
627+
const bool processed_part = ur_decoder_receive_part(qr_data->ctx, (const char*)qr_data->data);
630628

631-
// On hard failure, reset the decoder
632-
if (uris_failure_decoder(qr_data->ctx)) {
629+
// On hard failure (complete but unsuccessful - eg. checksum mismatch), reset the decoder
630+
// NOTE: the caller must fetch the decoder back from qr_data->ctx as it may be replaced here
631+
if (ur_decoder_is_complete(qr_data->ctx) && !ur_decoder_is_success(qr_data->ctx)) {
633632
JADE_LOGE("Failure to scan bcur data - resetting the decoder");
634-
urfree_placement_decoder(qr_data->ctx);
635-
urcreate_placement_decoder(qr_data->ctx, URDECODER_SIZE);
633+
ur_decoder_free(qr_data->ctx);
634+
qr_data->ctx = ur_decoder_new();
635+
JADE_ASSERT(qr_data->ctx);
636636
return false;
637637
}
638638

639639
// Update associated progress bar - be a bit defensive here
640-
const bool decoded = uris_success_decoder(qr_data->ctx);
641-
const size_t nreceived = urreceived_parts_count_decoder(qr_data->ctx);
640+
const bool decoded = ur_decoder_is_success(qr_data->ctx);
641+
const size_t nreceived = ur_decoder_received_parts_count(qr_data->ctx);
642642
if (processed_part && nreceived) {
643643
// NOTE: can only call 'expected' once we have received at least one part
644-
const size_t nexpected = urexpected_part_count_decoder(qr_data->ctx);
644+
const size_t nexpected = ur_decoder_expected_part_count(qr_data->ctx);
645645

646646
// If fully decoded show full bar - but if not fully decoded
647647
// don't show a full bar - pause at 'almost done' if required.
@@ -666,34 +666,37 @@ bool bcur_scan_qr(const char* prompt_text, char** output_type, uint8_t** output,
666666
JADE_INIT_OUT_PPTR(output);
667667
JADE_INIT_OUT_SIZE(output_len);
668668

669-
uint8_t urdecoder[URDECODER_SIZE];
670-
urcreate_placement_decoder(urdecoder, sizeof(urdecoder));
669+
ur_decoder_t* urdecoder = ur_decoder_new();
670+
JADE_ASSERT(urdecoder);
671671
progress_bar_t progress_bar = {};
672672
qr_data_t qr_data = { .len = 0, .is_valid = collect_any_bcur, .ctx = urdecoder, .progress_bar = &progress_bar };
673673

674674
// Scan qr code using the bcur decoder to collate multiple frames if required
675-
if (!jade_camera_scan_qr(&qr_data, prompt_text, QR_GUIDE_SHOW, help_url)) {
675+
// NOTE: collect_any_bcur() may replace the decoder (on hard failure), so
676+
// refresh the local pointer from qr_data.ctx once scanning ends.
677+
const bool scanned = jade_camera_scan_qr(&qr_data, prompt_text, QR_GUIDE_SHOW, help_url);
678+
urdecoder = qr_data.ctx;
679+
if (!scanned) {
676680
// User exited without completing scanning
677-
urfree_placement_decoder(urdecoder);
681+
ur_decoder_free(urdecoder);
678682
return false;
679683
}
680684

681685
// Copy output into output params - caller takes ownership
682-
if (uris_success_decoder(urdecoder)) {
686+
if (ur_decoder_is_success(urdecoder)) {
683687
// bcur message scanned - extract from decoder and return the payload
684-
uint8_t* result = NULL;
685-
size_t result_len = 0;
686-
const char* result_type = NULL;
687-
urresult_ur_decoder(urdecoder, &result, &result_len, &result_type);
688+
// NOTE: the result is borrowed from the decoder, and freed with it
689+
const ur_result_t* const result = ur_decoder_get_result(urdecoder);
688690
JADE_ASSERT(result);
689-
JADE_ASSERT(result_len);
690-
JADE_ASSERT(result_type);
691+
JADE_ASSERT(result->cbor_data);
692+
JADE_ASSERT(result->cbor_len);
693+
JADE_ASSERT(result->type);
691694

692695
// Copy payload and bc-ur type
693-
*output = JADE_MALLOC_PREFER_SPIRAM(result_len + offset);
694-
memcpy(*output + offset, result, result_len);
695-
*output_len = result_len + offset;
696-
*output_type = strdup(result_type);
696+
*output = JADE_MALLOC_PREFER_SPIRAM(result->cbor_len + offset);
697+
memcpy(*output + offset, result->cbor_data, result->cbor_len);
698+
*output_len = result->cbor_len + offset;
699+
*output_type = strdup(result->type);
697700
} else {
698701
// Not a bc-ur code - copy straight payload and append a nul-terminator.
699702
// Leave bc-ur type as NULL to indicate data was not a bc-ur payload.
@@ -705,7 +708,7 @@ bool bcur_scan_qr(const char* prompt_text, char** output_type, uint8_t** output,
705708
}
706709

707710
// Free the decoder and return true (as we scanned data successfully)
708-
urfree_placement_decoder(urdecoder);
711+
ur_decoder_free(urdecoder);
709712
return true;
710713
}
711714

@@ -741,9 +744,9 @@ void bcur_create_qr_icons(const uint8_t* payload, const size_t len, const char*
741744
JADE_LOGI("BC-UR encoding payload length %u as type %s", len, bcur_type);
742745
JADE_LOGI("Targetting qr-code version %u, capacity %u (alphanumeric mode), using max fragment size %u", qr_version,
743746
qrcode_alphanumeric_capacity, bcur_max_fragment_size);
744-
uint8_t encoder[URENCODER_SIZE];
745-
urcreate_placement_encoder(encoder, sizeof(encoder), bcur_type, payload, len, bcur_max_fragment_size, 0, 8);
746-
const size_t min_num_fragments = urseqlen_encoder(encoder); // the number of 'pure' data fragments
747+
ur_encoder_t* const encoder = ur_encoder_new(bcur_type, payload, len, bcur_max_fragment_size, 0, 8);
748+
JADE_ASSERT(encoder);
749+
const size_t min_num_fragments = ur_encoder_seq_len(encoder); // the number of 'pure' data fragments
747750
const size_t num_fragments = BCUR_NUM_FRAGMENTS(min_num_fragments); // add some fountain-code fragments
748751
JADE_ASSERT(num_fragments >= min_num_fragments);
749752
JADE_LOGI("Encoded payload length %u as %u pure fragments and %u fountain-code fragments", len, min_num_fragments,
@@ -753,11 +756,12 @@ void bcur_create_qr_icons(const uint8_t* payload, const size_t len, const char*
753756
uint8_t* qrbuffer = JADE_MALLOC(qrcode_getBufferSize(qr_version));
754757

755758
// Convert to 'num_fragments' qr-code icons
756-
const bool force_uppercase = true; // fetch bcur fragment as uppercase to conform to 'alphanumeric' qr mode
759+
// NOTE: fragments are uppercase, to conform to 'alphanumeric' qr mode
757760
Icon* const qr_icons = JADE_MALLOC(num_fragments * sizeof(Icon));
758761
for (int ifrag = 0; ifrag < num_fragments; ++ifrag) {
759762
char* fragment = NULL;
760-
urnext_part_encoder(encoder, force_uppercase, &fragment);
763+
const bool have_part = ur_encoder_next_part(encoder, &fragment);
764+
JADE_ASSERT(have_part);
761765
const size_t fragment_len = strlen(fragment);
762766
JADE_LOGI("Fragment %u, making qr-code icon with data (length: %u): %s", ifrag, fragment_len, fragment);
763767

@@ -768,15 +772,15 @@ void bcur_create_qr_icons(const uint8_t* payload, const size_t len, const char*
768772
QRCode qrcode;
769773
const int qret = qrcode_initText(&qrcode, qrbuffer, qr_version, BCUR_QR_ECC, fragment);
770774
JADE_ASSERT(qret == 0);
771-
urfree_encoded_encoder(fragment);
775+
free(fragment);
772776

773777
// Convert fragment to Icon
774778
qrcode_toIcon(&qrcode, qr_icons + ifrag, QR_SCALE_FACTOR[qr_version]);
775779
}
776-
JADE_ASSERT(uris_complete_encoder(encoder));
780+
JADE_ASSERT(ur_encoder_is_complete(encoder));
777781

778782
free(qrbuffer);
779-
urfree_placement_encoder(encoder);
783+
ur_encoder_free(encoder);
780784

781785
// Return the created icons
782786
*icons = qr_icons;
@@ -810,11 +814,11 @@ bool bcur_check_fragment_sizes(void)
810814
size_t max = 0;
811815

812816
for (size_t len = maxlen - 8; len < sizeof(payload); ++len) {
813-
uint8_t encoder[URENCODER_SIZE];
814-
urcreate_placement_encoder(encoder, sizeof(encoder), type, payload, len, maxlen, 0, 8);
817+
ur_encoder_t* const encoder = ur_encoder_new(type, payload, len, maxlen, 0, 8);
818+
JADE_ASSERT(encoder);
815819

816820
char* fragment = NULL;
817-
urnext_part_encoder(encoder, true, &fragment);
821+
ur_encoder_next_part(encoder, &fragment);
818822
JADE_ASSERT(fragment);
819823
const size_t fraglen = strlen(fragment);
820824
if (fraglen + 4 > capacity) {
@@ -827,8 +831,8 @@ bool bcur_check_fragment_sizes(void)
827831
if (fraglen > max) {
828832
max = fraglen;
829833
}
830-
urfree_encoded_encoder(fragment);
831-
urfree_placement_encoder(encoder);
834+
free(fragment);
835+
ur_encoder_free(encoder);
832836
}
833837
JADE_LOGI("max: %u (of target/limit %u)", max, capacity);
834838
}

main/process/mnemonic.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
#include "process_utils.h"
2121

22-
#include <cdecoder.h>
2322
#include <ctype.h>
2423

2524
#define MAX_NUM_FINAL_WORDS 128

0 commit comments

Comments
 (0)