codex32: use "cl" instead of "ms" as our HRP.

This was strongly recommended by Russell O'Connor: the "ms" implies that
it's a BIP-32 master secret, and this is CLN specific.

If we changed the hrp to "cln" it would be better, but apparently that
means we no longer fit in a "standard billfold metal wallet" (and
our code assumes a 2-byte prefix anyway).

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2023-08-02 12:27:43 +09:30
parent 67e43ea868
commit 0f6687ec7b
7 changed files with 58 additions and 28 deletions

View File

@@ -313,6 +313,7 @@ fail:
/* Return NULL if the codex32 is invalid */ /* Return NULL if the codex32 is invalid */
struct codex32 *codex32_decode(const tal_t *ctx, struct codex32 *codex32_decode(const tal_t *ctx,
const char *hrp,
const char *codex32str, const char *codex32str,
char **fail) char **fail)
{ {
@@ -335,8 +336,8 @@ struct codex32 *codex32_decode(const tal_t *ctx,
} }
parts->hrp = tal_strndup(parts, codex32str, sep - codex32str); parts->hrp = tal_strndup(parts, codex32str, sep - codex32str);
if (!streq(parts->hrp, "ms")) { if (hrp && !streq(parts->hrp, hrp)) {
*fail = tal_fmt(ctx, "Invalid HRP!"); *fail = tal_fmt(ctx, "Invalid hrp %s!", parts->hrp);
return tal_free(parts); return tal_free(parts);
} }
@@ -397,6 +398,7 @@ struct codex32 *codex32_decode(const tal_t *ctx,
/* Returns Codex32 encoded secret of the seed provided. */ /* Returns Codex32 encoded secret of the seed provided. */
const char *codex32_secret_encode(const tal_t *ctx, const char *codex32_secret_encode(const tal_t *ctx,
const char *hrp,
const char *id, const char *id,
const u32 threshold, const u32 threshold,
const u8 *seed, const u8 *seed,
@@ -404,7 +406,11 @@ const char *codex32_secret_encode(const tal_t *ctx,
char **bip93) char **bip93)
{ {
const struct checksum_engine *csum_engine; 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) if (threshold > 9 || threshold < 0 || threshold == 1)
return tal_fmt(ctx, "Invalid threshold %u", threshold); return tal_fmt(ctx, "Invalid threshold %u", threshold);

View File

@@ -32,17 +32,22 @@ struct codex32 {
* updated to contain the details extracted from the codex32 string. * updated to contain the details extracted from the codex32 string.
* fail: Pointer to a char *, that would be updated with the reason * fail: Pointer to a char *, that would be updated with the reason
* of failure in case this function returns a NULL. * 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, * Returns Parts to indicate decoding was successful. NULL is returned if decoding failed,
* with appropriate reason in the fail param * with appropriate reason in the fail param
*/ */
struct codex32 *codex32_decode(const tal_t *ctx, struct codex32 *codex32_decode(const tal_t *ctx,
const char *hrp,
const char *codex32str, const char *codex32str,
char **fail); char **fail);
/** Encode a seed into codex32 secret format. /** 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 * threshold: Threshold according to the bip93
* seed: The secret in u8* * seed: The secret in u8*
* seedlen: Length of the seed provided. * 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. * Returns an error string, or returns NULL and sets @bip93.
*/ */
const char *codex32_secret_encode(const tal_t *ctx, const char *codex32_secret_encode(const tal_t *ctx,
const char *hrp,
const char *id, const char *id,
const u32 threshold, const u32 threshold,
const u8 *seed, const u8 *seed,

View File

@@ -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(); } { fprintf(stderr, "towire_u8_array called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */ /* 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[]) int main(int argc, char *argv[])
{ {
common_setup(argv[0]); common_setup(argv[0]);
@@ -133,7 +146,7 @@ int main(int argc, char *argv[])
0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 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, assert(streq(c,
"ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma")); "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma"));
@@ -156,7 +169,7 @@ int main(int argc, char *argv[])
* * master node xprv: xprv9s21ZrQH143K3taPNekMd9oV5K6szJ8ND7vVh6fxicRUMDcChr3bFFzuxY8qP3xFFBL6DWc2uEYCfBFZ2nFWbAqKPhtCLRjgv78EZJDEfpL * * master node xprv: xprv9s21ZrQH143K3taPNekMd9oV5K6szJ8ND7vVh6fxicRUMDcChr3bFFzuxY8qP3xFFBL6DWc2uEYCfBFZ2nFWbAqKPhtCLRjgv78EZJDEfpL
*/ */
parts = codex32_decode(tmpctx, "ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw", &fail); parts = codex32_decode(tmpctx, NULL, "ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw", &fail);
if (parts) { if (parts) {
assert(streq(parts->hrp, "ms")); assert(streq(parts->hrp, "ms"));
assert(parts->threshold == 0); assert(parts->threshold == 0);
@@ -164,6 +177,7 @@ int main(int argc, char *argv[])
assert(parts->share_idx == 's'); assert(parts->share_idx == 's');
assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)),
"318c6318c6318c6318c6318c6318c631")); "318c6318c6318c6318c6318c6318c631"));
print_cl_vec("Test vector 1", parts);
} else { } else {
abort(); abort();
} }
@@ -183,10 +197,11 @@ int main(int argc, char *argv[])
* * master node xprv: xprv9s21ZrQH143K2NkobdHxXeyFDqE44nJYvzLFtsriatJNWMNKznGoGgW5UMTL4fyWtajnMYb5gEc2CgaKhmsKeskoi9eTimpRv2N11THhPTU * * master node xprv: xprv9s21ZrQH143K2NkobdHxXeyFDqE44nJYvzLFtsriatJNWMNKznGoGgW5UMTL4fyWtajnMYb5gEc2CgaKhmsKeskoi9eTimpRv2N11THhPTU
*/ */
parts = codex32_decode(tmpctx, "MS12NAMES6XQGUZTTXKEQNJSJZV4JV3NZ5K3KWGSPHUH6EVW", &fail); parts = codex32_decode(tmpctx, "ms", "MS12NAMES6XQGUZTTXKEQNJSJZV4JV3NZ5K3KWGSPHUH6EVW", &fail);
if(parts) { if(parts) {
assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), "d1808e096b35b209ca12132b264662a5")); assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), "d1808e096b35b209ca12132b264662a5"));
print_cl_vec("Test vector 2", parts);
} else { } else {
abort(); abort();
} }
@@ -226,10 +241,11 @@ int main(int argc, char *argv[])
}; };
for (size_t i = 0; i < ARRAY_SIZE(addr_vec3); i++) { 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) { if(parts) {
assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)),
"ffeeddccbbaa99887766554433221100")); "ffeeddccbbaa99887766554433221100"));
print_cl_vec("Test vector 3", parts);
} else { } else {
abort(); abort();
} }
@@ -283,10 +299,11 @@ int main(int argc, char *argv[])
}; };
for (size_t i = 0; i < ARRAY_SIZE(addr_vec4); i++) { 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) { if (parts) {
assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)),
"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100")); "ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100"));
print_cl_vec("Test vector 4", parts);
} else { } else {
abort(); 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) { if (parts) {
assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)), assert(streq(tal_hexstr(tmpctx, parts->payload, tal_bytelen(parts->payload)),
"dc5423251cb87175ff8110c8531d0952d8d73e1194e95b5f19d6f9df7c01111104c9baecdfea8cccc677fb9ddc8aec5553b86e528bcadfdcc201c17c638c47e9")); "dc5423251cb87175ff8110c8531d0952d8d73e1194e95b5f19d6f9df7c01111104c9baecdfea8cccc677fb9ddc8aec5553b86e528bcadfdcc201c17c638c47e9"));
print_cl_vec("Test vector 5", parts);
} else { } else {
abort(); abort();
} }
@@ -368,7 +386,7 @@ int main(int argc, char *argv[])
}; };
for (size_t i = 0; i < ARRAY_SIZE(addr_invalid); i++) { 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) { if (parts) {
abort(); abort();
} else { } else {
@@ -405,7 +423,7 @@ int main(int argc, char *argv[])
}; };
for (size_t i = 0; i < ARRAY_SIZE(addr_invalid1); i++) { 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) { if (parts) {
printf("payload == %ld\n", tal_bytelen(parts->payload)); printf("payload == %ld\n", tal_bytelen(parts->payload));
abort(); abort();
@@ -453,7 +471,7 @@ int main(int argc, char *argv[])
}; };
for (size_t i = 0; i < ARRAY_SIZE(addr_invalid2); i++) { 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) { if (parts) {
printf("payload %ld\n", tal_bytelen(parts->payload)); printf("payload %ld\n", tal_bytelen(parts->payload));
abort(); abort();
@@ -469,7 +487,7 @@ int main(int argc, char *argv[])
* * ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p * * ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p
*/ */
parts = codex32_decode(tmpctx, "ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p", &fail); parts = codex32_decode(tmpctx, NULL, "ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p", &fail);
if (parts) { if (parts) {
abort(); abort();
} else { } else {
@@ -482,7 +500,7 @@ int main(int argc, char *argv[])
* * ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg * * ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg
*/ */
parts = codex32_decode(tmpctx, "ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg", &fail); parts = codex32_decode(tmpctx, NULL, "ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg", &fail);
if (parts) { if (parts) {
abort(); abort();
} else { } else {
@@ -516,11 +534,11 @@ int main(int argc, char *argv[])
}; };
for (size_t i = 0; i < ARRAY_SIZE(addr_invalid3); i++) { 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) { if (parts) {
abort(); abort();
} else { } else {
assert(streq(fail, "Invalid HRP!") || assert(strstr(fail, "Invalid hrp ") ||
streq(fail, "Separator doesn't exist!")); streq(fail, "Separator doesn't exist!"));
} }
tal_free(parts); tal_free(parts);
@@ -548,7 +566,7 @@ int main(int argc, char *argv[])
}; };
for (size_t i = 0; i < ARRAY_SIZE(addr_invalid4); i++) { 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) { if (parts) {
abort(); abort();
} else { } else {

View File

@@ -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: The payload consists of the following information:
```json ```json
{ {
"codex32": "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma" "codex32": "cl10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqjdsjnzedu43ns"
} }
``` ```

View File

@@ -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) static char *opt_set_codex32(const char *arg, struct lightningd *ld)
{ {
char *err; char *err;
struct codex32 *parts = codex32_decode(tmpctx, arg, &err); struct codex32 *parts = codex32_decode(tmpctx, "cl", arg, &err);
if (!parts) { if (!parts) {
return err; return err;

View File

@@ -1350,14 +1350,14 @@ def test_recover(node_factory, bitcoind):
""" """
# Start the node with --recovery with valid codex32 secret # Start the node with --recovery with valid codex32 secret
l1 = node_factory.get_node(start=False, 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")) os.unlink(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret"))
l1.daemon.start() l1.daemon.start()
cmd_line = ["tools/hsmtool", "getcodexsecret", os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")] 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') out = subprocess.check_output(cmd_line + ["leet", "0"]).decode('utf-8')
assert out == "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma\n" assert out == "cl10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqjdsjnzedu43ns\n"
# Check bad ids. # Check bad ids.
out = subprocess.run(cmd_line + ["lee", "0"], stderr=subprocess.PIPE, timeout=TIMEOUT) 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")) os.unlink(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "lightningd.sqlite3"))
# Node should throw error to recover flag if HSM already exists. # 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) l1.daemon.start(wait_for_initialized=False, stderr_redir=True)
# Will exit with failure code. # 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")) 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) l1.daemon.start(wait_for_initialized=False, stderr_redir=True)
assert l1.daemon.wait() == 1 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.daemon.opts.pop("recover")
l1.start() l1.start()

View File

@@ -254,7 +254,7 @@ static int make_codexsecret(const char *hsm_secret_path,
const char *err; const char *err;
get_hsm_secret(&hsm_secret, hsm_secret_path); 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) if (err)
errx(ERROR_USAGE, "%s", err); errx(ERROR_USAGE, "%s", err);