Skip to content

Commit 5cbc42a

Browse files
author
Jamie C. Driver
committed
usbstorage: sign psbt file from usb storage
1 parent e604ff0 commit 5cbc42a

File tree

3 files changed

+184
-1
lines changed

3 files changed

+184
-1
lines changed

main/process/dashboard.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2330,7 +2330,10 @@ static void handle_settings(const bool startup_menu)
23302330
break;
23312331

23322332
case BTN_SETTINGS_USBSTORAGE_SIGN:
2333-
// FIXME: implement
2333+
usbstorage_sign_psbt(NULL);
2334+
2335+
// NOTE: signing cleans up other activities, so need to recreate menu
2336+
act = make_usbstorage_settings_activity(keychain_get());
23342337
break;
23352338

23362339
case BTN_SETTINGS_USBSTORAGE_EXPORT:

main/usbhmsc/usbmode.c

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
#include "../gui.h"
33
#include "../jade_assert.h"
44
#include "../jade_tasks.h"
5+
#include "../jade_wally_verify.h"
6+
#include "../keychain.h"
57
#include "../process.h"
68
#include "../serial.h"
79
#include "../ui.h"
810
#include "../utils/malloc_ext.h"
11+
#include "../utils/network.h"
912
#include "usbhmsc.h"
13+
#include <ctype.h>
1014
#include <dirent.h>
1115
#include <errno.h>
1216
#include <stdbool.h>
@@ -15,15 +19,28 @@
1519
#include <sys/stat.h>
1620
#include <sys/types.h>
1721

22+
#include <wally_psbt.h>
23+
1824
gui_activity_t* make_usb_connect_activity(const char* title);
1925
void await_qr_help_activity(const char* url);
2026

27+
// PSBT serialisation functions
28+
bool deserialise_psbt(const uint8_t* bytes, size_t bytes_len, struct wally_psbt** psbt_out);
29+
bool serialise_psbt(const struct wally_psbt* psbt, uint8_t** output, size_t* output_len);
30+
int sign_psbt(const char* network, struct wally_psbt* psbt, const char** errmsg);
31+
2132
#define MAX_FILENAME_SIZE 256
2233
#define MAX_FILE_ENTRIES 64
2334

35+
#define MIN_PSBT_FILE_SIZE 32
36+
#define MAX_PSBT_FILE_SIZE MAX_INPUT_MSG_SIZE
37+
2438
static const char FW_SUFFIX[] = "_fw.bin";
2539
static const char HASH_SUFFIX[] = ".hash";
2640

41+
static const char PSBT_SUFFIX[] = ".psbt";
42+
static const char PSBT_TXT_SUFFIX[] = ".psbt.txt";
43+
2744
#define STR_ENDSWITH(str, str_len, suffix, suffix_len) \
2845
(str && str_len > suffix_len && str[str_len] == '\0' && !memcmp(str + str_len - suffix_len, suffix, suffix_len))
2946

@@ -76,6 +93,24 @@ static size_t read_file_to_buffer(const char* filename, uint8_t* buffer, size_t
7693
return bytes_read;
7794
}
7895

96+
static size_t write_buffer_to_file(const char* filename, const uint8_t* buffer, size_t buf_len)
97+
{
98+
JADE_ASSERT(filename);
99+
JADE_ASSERT(buffer);
100+
JADE_ASSERT(buf_len);
101+
102+
FILE* fp = fopen(filename, "wb");
103+
if (fp == NULL) {
104+
return 0;
105+
}
106+
107+
const size_t bytes_written = fwrite(buffer, 1, buf_len, fp);
108+
fclose(fp);
109+
110+
JADE_ASSERT(bytes_written == buf_len);
111+
return bytes_written;
112+
}
113+
79114
// Display list of files, and return if user selects one
80115
// Must be passed predicate to filter filenames
81116
// NOTE: 'extra_path' (input) is relative to the mount point, but 'filename' (output) will be the full path including
@@ -668,3 +703,145 @@ bool usbstorage_firmware_ota(const char* extra_path)
668703
const bool is_async = true;
669704
return handle_usbstorage_action("Firmware Upgrade", initiate_usb_ota, extra_path, is_async);
670705
}
706+
707+
// Sign PSBT
708+
709+
static bool is_psbt_binary_file(const char* path, const char* filename, const size_t filename_len)
710+
{
711+
return STR_ENDSWITH(filename, filename_len, PSBT_SUFFIX, strlen(PSBT_SUFFIX));
712+
}
713+
714+
static bool is_psbt_txt_file(const char* path, const char* filename, const size_t filename_len)
715+
{
716+
return STR_ENDSWITH(filename, filename_len, PSBT_TXT_SUFFIX, strlen(PSBT_TXT_SUFFIX));
717+
}
718+
719+
static bool is_psbt_file(const char* path, const char* filename, const size_t filename_len)
720+
{
721+
return is_psbt_binary_file(path, filename, filename_len) || is_psbt_txt_file(path, filename, filename_len);
722+
}
723+
724+
static bool sign_usb_psbt(const char* extra_path)
725+
{
726+
// extra_path is optional
727+
728+
char filename[MAX_FILENAME_SIZE];
729+
if (!select_file_from_filtered_list("Select PSBT", extra_path, is_psbt_file, filename, sizeof(filename))) {
730+
return false;
731+
}
732+
733+
// Sanity check file size
734+
size_t psbt_len = get_file_size(filename);
735+
if (psbt_len < MIN_PSBT_FILE_SIZE) {
736+
const char* message[] = { "Invalid PSBT file" };
737+
await_error_activity(message, 1);
738+
return false;
739+
}
740+
if (psbt_len > MAX_PSBT_FILE_SIZE) {
741+
const char* message[] = { "PSBT file too large" };
742+
await_error_activity(message, 1);
743+
return false;
744+
}
745+
746+
bool retval = false;
747+
struct wally_psbt* psbt = NULL;
748+
const size_t filename_len = strlen(filename);
749+
const bool b64 = is_psbt_txt_file("", filename, filename_len);
750+
751+
uint8_t* psbt_bytes = JADE_MALLOC_PREFER_SPIRAM(psbt_len);
752+
if (b64) {
753+
// Load from base64 text file
754+
char* const psbt64 = JADE_MALLOC_PREFER_SPIRAM(psbt_len + 1);
755+
const size_t bytes_read = read_file_to_buffer(filename, (uint8_t*)psbt64, psbt_len);
756+
JADE_ASSERT(bytes_read == psbt_len);
757+
758+
// Add trailing terminator and trim any trailing whitespace
759+
do {
760+
psbt64[psbt_len] = '\0';
761+
} while (isspace((unsigned char)(psbt64[--psbt_len])));
762+
763+
size_t written = 0;
764+
const int wret = wally_base64_to_bytes(psbt64, 0, psbt_bytes, psbt_len, &written);
765+
free(psbt64);
766+
767+
if (wret != WALLY_OK || !written || written > psbt_len) {
768+
const char* message[] = { "Failed to decode", "PSBT base64" };
769+
await_error_activity(message, 2);
770+
goto cleanup;
771+
}
772+
psbt_len = written;
773+
} else {
774+
// Read bytes from binary file
775+
const size_t bytes_read = read_file_to_buffer(filename, psbt_bytes, psbt_len);
776+
JADE_ASSERT(bytes_read == psbt_len);
777+
}
778+
779+
// Deserialise bytes
780+
if (!deserialise_psbt(psbt_bytes, psbt_len, &psbt) || !psbt) {
781+
const char* message[] = { "Failed to load PSBT" };
782+
await_error_activity(message, 1);
783+
goto cleanup;
784+
}
785+
786+
// Free bytes loaded from file
787+
free(psbt_bytes);
788+
psbt_bytes = NULL;
789+
psbt_len = 0;
790+
791+
// Sign PSBT
792+
const char* errmsg = NULL;
793+
const char* network = keychain_get_network_type_restriction() == NETWORK_TYPE_TEST ? TAG_TESTNET : TAG_MAINNET;
794+
const int errcode = sign_psbt(network, psbt, &errmsg);
795+
if (errcode) {
796+
if (errcode != CBOR_RPC_USER_CANCELLED) {
797+
const char* message[] = { errmsg };
798+
await_error_activity(message, 1);
799+
}
800+
goto cleanup;
801+
}
802+
803+
// Write to file
804+
// FIXME: create a new file ?
805+
if (b64) {
806+
// Encode to base64
807+
char* psbt64 = NULL;
808+
if (wally_psbt_to_base64(psbt, 0, &psbt64) != WALLY_OK || !psbt64) {
809+
const char* message[] = { "Failed to", "serialise PSBT" };
810+
await_error_activity(message, 2);
811+
goto cleanup;
812+
}
813+
write_buffer_to_file(filename, (const uint8_t*)psbt64, strlen(psbt64));
814+
JADE_WALLY_VERIFY(wally_free_string(psbt64));
815+
} else {
816+
// Serialise signed PSBT to bytes
817+
if (!serialise_psbt(psbt, &psbt_bytes, &psbt_len)) {
818+
const char* message[] = { "Failed to", "serialise PSBT" };
819+
await_error_activity(message, 2);
820+
goto cleanup;
821+
}
822+
write_buffer_to_file(filename, psbt_bytes, psbt_len);
823+
}
824+
825+
const size_t mount_point_len = strlen(USBSTORAGE_MOUNT_POINT);
826+
JADE_ASSERT(filename_len > mount_point_len);
827+
JADE_ASSERT(!memcmp(filename, USBSTORAGE_MOUNT_POINT, mount_point_len));
828+
const char* message[] = { "PSBT file saved:", filename + mount_point_len + 1 };
829+
await_error_activity(message, 2);
830+
retval = true;
831+
832+
cleanup:
833+
JADE_WALLY_VERIFY(wally_psbt_free(psbt));
834+
free(psbt_bytes);
835+
836+
return retval;
837+
}
838+
839+
// Sign PSBT file, and write updated file back to the usb-storage directory.
840+
// Accepts binary PSBT file ('xxx.psbt') or base64-encoded PSBT file ('xxx.psbt.txt').
841+
// After any signatures are added, the file is written in the same format.
842+
bool usbstorage_sign_psbt(const char* extra_path)
843+
{
844+
// extra_path is optional
845+
const bool is_async = false;
846+
return handle_usbstorage_action("Sign PSBT", sign_usb_psbt, extra_path, is_async);
847+
}

main/usbhmsc/usbmode.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
// Initiate an OTA fw upgrade from compressed fw and hash file
77
bool usbstorage_firmware_ota(const char* extra_path);
88

9+
// Sign PSBT file, and write updated file
10+
bool usbstorage_sign_psbt(const char* extra_path);
11+
912
#endif /* USBMODE_H_ */

0 commit comments

Comments
 (0)