diff --git a/common/codex32.c b/common/codex32.c index bb1edac6b..f74587a63 100644 --- a/common/codex32.c +++ b/common/codex32.c @@ -313,6 +313,7 @@ fail: /* Return NULL if the codex32 is invalid */ struct codex32 *codex32_decode(const tal_t *ctx, + const char *hrp, const char *codex32str, char **fail) { @@ -335,8 +336,8 @@ struct codex32 *codex32_decode(const tal_t *ctx, } parts->hrp = tal_strndup(parts, codex32str, sep - codex32str); - if (!streq(parts->hrp, "ms")) { - *fail = tal_fmt(ctx, "Invalid HRP!"); + if (hrp && !streq(parts->hrp, hrp)) { + *fail = tal_fmt(ctx, "Invalid hrp %s!", parts->hrp); return tal_free(parts); } @@ -397,6 +398,7 @@ struct codex32 *codex32_decode(const tal_t *ctx, /* Returns Codex32 encoded secret of the seed provided. */ const char *codex32_secret_encode(const tal_t *ctx, + const char *hrp, const char *id, const u32 threshold, const u8 *seed, @@ -404,7 +406,11 @@ const char *codex32_secret_encode(const tal_t *ctx, char **bip93) { const struct checksum_engine *csum_engine; - const char *hrp = "ms"; + + /* FIXME: Our code assumes a two-letter HRP! Larger won't allow a + * 128-bit secret in a "standard billfold metal wallet" acording to + * Russell O'Connor */ + assert(strlen(hrp) == 2); if (threshold > 9 || threshold < 0 || threshold == 1) return tal_fmt(ctx, "Invalid threshold %u", threshold); diff --git a/common/codex32.h b/common/codex32.h index 0691d3538..765b7fd39 100644 --- a/common/codex32.h +++ b/common/codex32.h @@ -32,17 +32,22 @@ struct codex32 { * updated to contain the details extracted from the codex32 string. * fail: Pointer to a char *, that would be updated with the reason * of failure in case this function returns a NULL. - * In: input: Pointer to a null-terminated codex32 string. + * In: ctx: Allocation context for *fail or return. + * hrp: If non-NULL, a hrp which must match. + * codex32str: Pointer to a nul-terminated codex32 string. + * * Returns Parts to indicate decoding was successful. NULL is returned if decoding failed, * with appropriate reason in the fail param */ struct codex32 *codex32_decode(const tal_t *ctx, + const char *hrp, const char *codex32str, char **fail); /** Encode a seed into codex32 secret format. * - * In: input: id: Valid 4 char string identifying the secret + * In: input: hrp: 2 character human-readable-prefix + * id: Valid 4 char string identifying the secret * threshold: Threshold according to the bip93 * seed: The secret in u8* * seedlen: Length of the seed provided. @@ -51,6 +56,7 @@ struct codex32 *codex32_decode(const tal_t *ctx, * Returns an error string, or returns NULL and sets @bip93. */ const char *codex32_secret_encode(const tal_t *ctx, + const char *hrp, const char *id, const u32 threshold, const u8 *seed, diff --git a/common/test/run-codex32.c b/common/test/run-codex32.c index 84cb0e7ae..5e0c78041 100644 --- a/common/test/run-codex32.c +++ b/common/test/run-codex32.c @@ -119,6 +119,19 @@ void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNE { fprintf(stderr, "towire_u8_array called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ +/* Print the "cl" variant of the vector */ +static void print_cl_vec(const char *desc, const struct codex32 *parts) +{ + const char *err; + char *bip93; + + err = codex32_secret_encode(tmpctx, "cl", parts->id, parts->threshold, + parts->payload, tal_bytelen(parts->payload), + &bip93); + assert(!err); + printf("%s: %s\n", desc, bip93); +} + int main(int argc, char *argv[]) { common_setup(argv[0]); @@ -133,7 +146,7 @@ int main(int argc, char *argv[]) 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, }; - assert(codex32_secret_encode(tmpctx, "leet", 0, seed_b, ARRAY_SIZE(seed_b), &c) == NULL); + assert(codex32_secret_encode(tmpctx, "ms", "leet", 0, seed_b, ARRAY_SIZE(seed_b), &c) == NULL); assert(streq(c, "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma")); @@ -156,7 +169,7 @@ int main(int argc, char *argv[]) * * master node xprv: xprv9s21ZrQH143K3taPNekMd9oV5K6szJ8ND7vVh6fxicRUMDcChr3bFFzuxY8qP3xFFBL6DWc2uEYCfBFZ2nFWbAqKPhtCLRjgv78EZJDEfpL */ - parts = codex32_decode(tmpctx, "ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw", &fail); + parts = codex32_decode(tmpctx, NULL, "ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw", &fail); if (parts) { assert(streq(parts->hrp, "ms")); assert(parts->threshold == 0); @@ -164,6 +177,7 @@ int main(int argc, char *argv[]) assert(parts->share_idx == 's'); assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), "318c6318c6318c6318c6318c6318c631")); + print_cl_vec("Test vector 1", parts); } else { abort(); } @@ -183,10 +197,11 @@ int main(int argc, char *argv[]) * * master node xprv: xprv9s21ZrQH143K2NkobdHxXeyFDqE44nJYvzLFtsriatJNWMNKznGoGgW5UMTL4fyWtajnMYb5gEc2CgaKhmsKeskoi9eTimpRv2N11THhPTU */ - parts = codex32_decode(tmpctx, "MS12NAMES6XQGUZTTXKEQNJSJZV4JV3NZ5K3KWGSPHUH6EVW", &fail); + parts = codex32_decode(tmpctx, "ms", "MS12NAMES6XQGUZTTXKEQNJSJZV4JV3NZ5K3KWGSPHUH6EVW", &fail); if(parts) { assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), "d1808e096b35b209ca12132b264662a5")); + print_cl_vec("Test vector 2", parts); } else { abort(); } @@ -226,10 +241,11 @@ int main(int argc, char *argv[]) }; for (size_t i = 0; i < ARRAY_SIZE(addr_vec3); i++) { - parts = codex32_decode(tmpctx, addr_vec3[i], &fail); + parts = codex32_decode(tmpctx, NULL, addr_vec3[i], &fail); if(parts) { assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), "ffeeddccbbaa99887766554433221100")); + print_cl_vec("Test vector 3", parts); } else { abort(); } @@ -283,10 +299,11 @@ int main(int argc, char *argv[]) }; for (size_t i = 0; i < ARRAY_SIZE(addr_vec4); i++) { - parts = codex32_decode(tmpctx, addr_vec4[i], &fail); + parts = codex32_decode(tmpctx, NULL, addr_vec4[i], &fail); if (parts) { assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), "ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100")); + print_cl_vec("Test vector 4", parts); } else { abort(); } @@ -305,10 +322,11 @@ int main(int argc, char *argv[]) * */ - parts = codex32_decode(tmpctx, "MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK", &fail); + parts = codex32_decode(tmpctx, NULL, "MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK", &fail); if (parts) { assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), "dc5423251cb87175ff8110c8531d0952d8d73e1194e95b5f19d6f9df7c01111104c9baecdfea8cccc677fb9ddc8aec5553b86e528bcadfdcc201c17c638c47e9")); + print_cl_vec("Test vector 5", parts); } else { abort(); } @@ -368,7 +386,7 @@ int main(int argc, char *argv[]) }; for (size_t i = 0; i < ARRAY_SIZE(addr_invalid); i++) { - parts = codex32_decode(tmpctx, addr_invalid[i], &fail); + parts = codex32_decode(tmpctx, NULL, addr_invalid[i], &fail); if (parts) { abort(); } else { @@ -405,7 +423,7 @@ int main(int argc, char *argv[]) }; for (size_t i = 0; i < ARRAY_SIZE(addr_invalid1); i++) { - parts = codex32_decode(tmpctx, addr_invalid1[i], &fail); + parts = codex32_decode(tmpctx, NULL, addr_invalid1[i], &fail); if (parts) { printf("payload == %ld\n", tal_bytelen(parts->payload)); abort(); @@ -453,7 +471,7 @@ int main(int argc, char *argv[]) }; for (size_t i = 0; i < ARRAY_SIZE(addr_invalid2); i++) { - parts = codex32_decode(tmpctx, addr_invalid2[i], &fail); + parts = codex32_decode(tmpctx, NULL, addr_invalid2[i], &fail); if (parts) { printf("payload %ld\n", tal_bytelen(parts->payload)); abort(); @@ -469,7 +487,7 @@ int main(int argc, char *argv[]) * * ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p */ - parts = codex32_decode(tmpctx, "ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p", &fail); + parts = codex32_decode(tmpctx, NULL, "ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p", &fail); if (parts) { abort(); } else { @@ -482,7 +500,7 @@ int main(int argc, char *argv[]) * * ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg */ - parts = codex32_decode(tmpctx, "ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg", &fail); + parts = codex32_decode(tmpctx, NULL, "ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg", &fail); if (parts) { abort(); } else { @@ -516,11 +534,11 @@ int main(int argc, char *argv[]) }; for (size_t i = 0; i < ARRAY_SIZE(addr_invalid3); i++) { - parts = codex32_decode(tmpctx, addr_invalid3[i], &fail); + parts = codex32_decode(tmpctx, "ms", addr_invalid3[i], &fail); if (parts) { abort(); } else { - assert(streq(fail, "Invalid HRP!") || + assert(strstr(fail, "Invalid hrp ") || streq(fail, "Separator doesn't exist!")); } tal_free(parts); @@ -548,7 +566,7 @@ int main(int argc, char *argv[]) }; for (size_t i = 0; i < ARRAY_SIZE(addr_invalid4); i++) { - parts = codex32_decode(tmpctx, addr_invalid4[i], &fail); + parts = codex32_decode(tmpctx, NULL, addr_invalid4[i], &fail); if (parts) { abort(); } else { diff --git a/doc/developers-guide/plugin-development/hooks.md b/doc/developers-guide/plugin-development/hooks.md index 4bc15c9e0..8ee40495a 100644 --- a/doc/developers-guide/plugin-development/hooks.md +++ b/doc/developers-guide/plugin-development/hooks.md @@ -64,7 +64,7 @@ This hook is called whenever the node is started using the --recovery flag. So b The payload consists of the following information: ```json { - "codex32": "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma" + "codex32": "cl10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqjdsjnzedu43ns" } ``` @@ -628,4 +628,4 @@ The payload for a call follows this format: All fields shown here are optional. -We suggest just returning `{'result': 'continue'}`; any other result will cause the message not to be handed to any other hooks. \ No newline at end of file +We suggest just returning `{'result': 'continue'}`; any other result will cause the message not to be handed to any other hooks. diff --git a/lightningd/options.c b/lightningd/options.c index cd1b1a464..2e50a5ce3 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -1275,7 +1275,7 @@ static char *opt_set_announce_dns(const char *optarg, struct lightningd *ld) static char *opt_set_codex32(const char *arg, struct lightningd *ld) { char *err; - struct codex32 *parts = codex32_decode(tmpctx, arg, &err); + struct codex32 *parts = codex32_decode(tmpctx, "cl", arg, &err); if (!parts) { return err; diff --git a/tests/test_misc.py b/tests/test_misc.py index 010a8681f..ccba214c3 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1350,14 +1350,14 @@ def test_recover(node_factory, bitcoind): """ # Start the node with --recovery with valid codex32 secret l1 = node_factory.get_node(start=False, - options={"recover": "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma"}) + options={"recover": "cl10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqjdsjnzedu43ns"}) os.unlink(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")) l1.daemon.start() cmd_line = ["tools/hsmtool", "getcodexsecret", os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")] out = subprocess.check_output(cmd_line + ["leet", "0"]).decode('utf-8') - assert out == "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma\n" + assert out == "cl10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqjdsjnzedu43ns\n" # Check bad ids. out = subprocess.run(cmd_line + ["lee", "0"], stderr=subprocess.PIPE, timeout=TIMEOUT) @@ -1388,7 +1388,7 @@ def test_recover(node_factory, bitcoind): os.unlink(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "lightningd.sqlite3")) # Node should throw error to recover flag if HSM already exists. - l1.daemon.opts['recover'] = "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma" + l1.daemon.opts['recover'] = "cl10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqjdsjnzedu43ns" l1.daemon.start(wait_for_initialized=False, stderr_redir=True) # Will exit with failure code. @@ -1397,10 +1397,10 @@ def test_recover(node_factory, bitcoind): os.unlink(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")) - l1.daemon.opts.update({"recover": "MS12NAMES6XQGUZTTXKEQNJSJZV4JV3NZ5K3KWGSPHUH6EVW"}) + l1.daemon.opts.update({"recover": "CL10LEETSLLHDMN9M42VCSAMX24ZRXGS3QQAT3LTDVAKMT73"}) l1.daemon.start(wait_for_initialized=False, stderr_redir=True) assert l1.daemon.wait() == 1 - assert l1.daemon.is_in_stderr(r"Expected 32 Byte secret: d1808e096b35b209ca12132b264662a5") + assert l1.daemon.is_in_stderr(r"Expected 32 Byte secret: ffeeddccbbaa99887766554433221100") l1.daemon.opts.pop("recover") l1.start() diff --git a/tools/hsmtool.c b/tools/hsmtool.c index 21a4533ea..4b2667d1b 100644 --- a/tools/hsmtool.c +++ b/tools/hsmtool.c @@ -254,7 +254,7 @@ static int make_codexsecret(const char *hsm_secret_path, const char *err; get_hsm_secret(&hsm_secret, hsm_secret_path); - err = codex32_secret_encode(tmpctx, id, 0, hsm_secret.data, 32, &bip93); + err = codex32_secret_encode(tmpctx, "cl", id, 0, hsm_secret.data, 32, &bip93); if (err) errx(ERROR_USAGE, "%s", err);