utils: add util.c and functions is_potential_green_*_path()
What changed, and why it matters
This commit adds a new utility file with helper functions that validate whether a Bitcoin key derivation path looks like one used by Blockstream Green wallets. There is no evidence in the commit that this fixes a security bug; it appears to be a new helper for recognizing Green wallet paths.
No immediate action required. Treat as routine code addition. Review future commits that call these helpers to ensure path validation is applied consistently and correctly.
Security signals we found
No security-relevant signal in the diff itself
New path-validation helpers may be defensive, but no vulnerability is described or fixed
Evidence from the diff
The commit introduces main/utils/util.c and three functions: is_potential_green_user_path(), is_potential_green_recovery_path(), and is_potential_green_server_path(). These functions inspect BIP32 derivation paths and decide whether they match the known Blockstream Green path structures, optionally returning the inferred subaccount index. The commit also wires the new file into the amalgamated build and adds related constants to wallet.h. No callers of these functions are added in this commit, and the diff shows no patch to existing behavior.
Changed components
main/utils/util.cmain/utils/util.hmain/amalgamated.cmain/wallet.hInspect captured patch +76 / −0
diff --git a/main/amalgamated.c b/main/amalgamated.c
index 78d6fec..99d3dd0 100644
--- a/main/amalgamated.c
+++ b/main/amalgamated.c
@@ -180,6 +180,7 @@ void __wrap_abort(void);
#include "./utils/temporary_stack.c"
#endif // CONFIG_LIBJADE
#include "./utils/urldecode.c"
+#include "./utils/util.c"
#include "./utils/wally_ext.c"
#include "./versioninfo.c"
#include "./wallet.c"
diff --git a/main/utils/util.c b/main/utils/util.c
new file mode 100644
index 0000000..6386078
--- /dev/null
+++ b/main/utils/util.c
@@ -0,0 +1,69 @@
+#ifndef AMALGAMATED_BUILD
+#include "util.h"
+#include "../wallet.h"
+
+// Green user path:
+// Main account:
+// m/1/pointer
+// Subaccounts:
+// m/3'/subaccount'/1/pointer
+bool is_potential_green_user_path(const uint32_t* path, const size_t path_len, uint32_t* subaccount_out)
+{
+ if (path_len != GA_USER_PATH_MAX_LEN) {
+ // Main account path looks just like a recovery path, so check that
+ if (is_potential_green_recovery_path(path, path_len)) {
+ *subaccount_out = 0; // Main account
+ return true;
+ }
+ return false;
+ }
+ if (path[0] == harden(3) && ishardened(path[1]) && path[2] == 1 && path[3] && !ishardened(path[3])) {
+ *subaccount_out = unharden(path[1]); // Subaccount
+ return true;
+ }
+ return false;
+}
+
+// Green (user) recovery path:
+// Subaccounts:
+// m/1/pointer
+// NOTE: The main account (subaccount 0) cannot be a recovery path,
+// so there is no main account path formulation.
+bool is_potential_green_recovery_path(const uint32_t* path, const size_t path_len)
+{
+ if (path_len != GA_RECOVERY_PATH_LEN) {
+ return false;
+ }
+ return path[0] == 1 && !ishardened(path[1]);
+}
+
+// Green server path
+// Main account
+// m/1/gait_path/pointer
+// Subaccounts:
+// m/3/gait_path/subaccount/pointer
+bool is_potential_green_server_path(const uint32_t* path, const size_t path_len, uint32_t* subaccount_out)
+{
+ if (path_len == MAX_GASERVICE_PATH_LEN - 1) {
+ if (path[0] != 1) {
+ return false; // Main account indicator not present
+ }
+ } else if (path_len == MAX_GASERVICE_PATH_LEN) {
+ if (path[0] != 3) {
+ return false; // Subaccount indicator not present
+ }
+ } else {
+ return false;
+ }
+ const size_t tail_len = path_len == MAX_GASERVICE_PATH_LEN ? 2 : 1;
+ for (size_t i = 1; i < path_len; ++i) {
+ if (i < path_len - tail_len && path[i] > 0xffff) {
+ return false; // Not a Green server path element
+ } else if (ishardened(path[i])) {
+ return false; // Hardened subaccount or pointer not allowed
+ }
+ }
+ *subaccount_out = path_len == MAX_GASERVICE_PATH_LEN ? path[path_len - 2] : 0;
+ return true;
+}
+#endif // AMALGAMATED_BUILD
diff --git a/main/utils/util.h b/main/utils/util.h
index 80159ea..bb370a9 100644
--- a/main/utils/util.h
+++ b/main/utils/util.h
@@ -104,4 +104,8 @@ static inline size_t path_get_unhardened_tail_index(const uint32_t* path, const
return path_tail_start;
}
+bool is_potential_green_user_path(const uint32_t* path, size_t path_len, uint32_t* subaccount_out);
+bool is_potential_green_recovery_path(const uint32_t* path, size_t path_len);
+bool is_potential_green_server_path(const uint32_t* path, size_t path_len, uint32_t* subaccount_out);
+
#endif /* UTIL_H_ */
diff --git a/main/wallet.h b/main/wallet.h
index 36cc018..48c3683 100644
--- a/main/wallet.h
+++ b/main/wallet.h
@@ -43,6 +43,8 @@ typedef struct {
#define GASERVICE_ROOT_PATH_LEN (1 + 32)
#define MAX_GASERVICE_PATH_TAIL_LEN (1 + 1)
#define MAX_GASERVICE_PATH_LEN (GASERVICE_ROOT_PATH_LEN + MAX_GASERVICE_PATH_TAIL_LEN)
+#define GA_USER_PATH_MAX_LEN 4
+#define GA_RECOVERY_PATH_LEN 2
// 'm' + ( ('/' + <10 digit number>[+ ']) * n) + '\0'
#define MAX_PATH_STR_LEN(max_path_elems) (1 + ((1 + 10 + 1) * max_path_elems) + 1)
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.