diff --git a/common/json_parse.c b/common/json_parse.c index 5c1c68310b58..570f15e7b6a7 100644 --- a/common/json_parse.c +++ b/common/json_parse.c @@ -507,29 +507,21 @@ const char *json_scan(const tal_t *ctx, bool json_to_bitcoin_amount(const char *buffer, const jsmntok_t *tok, uint64_t *satoshi) { - char *end; - unsigned long btc, sat; + const char *start = buffer + tok->start; + size_t len = tok->end - tok->start; + const char *dot = memchr(start, '.', len); + u64 btc, sat = 0; - errno = 0; - btc = strtoul(buffer + tok->start, &end, 10); - if (btc == ULONG_MAX && errno == ERANGE) - return false; - if (end != buffer + tok->end) { - /* Expect always 8 decimal places. */ - if (*end != '.' || buffer + tok->end - end != 9) - return false; - sat = strtoul(end+1, &end, 10); - if (sat == ULONG_MAX && errno == ERANGE) - return false; - if (end != buffer + tok->end) + if (dot) { + if (start + len - (dot + 1) != 8 || !str_to_u64(dot + 1, 8, &sat)) return false; - } else - sat = 0; - - *satoshi = btc * (uint64_t)100000000 + sat; - if (*satoshi != btc * (uint64_t)100000000 + sat) + len = dot - start; + } + if (!str_to_u64(start, len, &btc)) return false; - + if (mul_overflows_u64(btc, 100000000) || add_overflows_u64(btc * 100000000, sat)) + return false; + *satoshi = btc * 100000000 + sat; return true; } diff --git a/common/test/run-json.c b/common/test/run-json.c index 1690972b8eb0..bc44d2c50a6e 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -51,7 +51,7 @@ void towire_tlv(u8 **pptr UNNEEDED, // issue #577 -static void do_json_tok_bitcoin_amount(const char* val, uint64_t expected) +static void do_json_tok_bitcoin_amount(const char *val, bool ok, uint64_t expected) { uint64_t amount; jsmntok_t tok; @@ -59,10 +59,12 @@ static void do_json_tok_bitcoin_amount(const char* val, uint64_t expected) tok.start = 0; tok.end = strlen(val); - fprintf(stderr, "do_json_tok_bitcoin_amount(\"%s\", %"PRIu64"): ", val, expected); + fprintf(stderr, "do_json_tok_bitcoin_amount(\"%s\", %s, %"PRIu64"): ", + val, ok ? "true" : "false", expected); - assert(json_to_bitcoin_amount(val, &tok, &amount) == true); - assert(amount == expected); + assert(json_to_bitcoin_amount(val, &tok, &amount) == ok); + if (ok) + assert(amount == expected); fprintf(stderr, "ok\n"); } @@ -70,13 +72,31 @@ static void do_json_tok_bitcoin_amount(const char* val, uint64_t expected) static int test_json_tok_bitcoin_amount(void) { - do_json_tok_bitcoin_amount("0.00000001", 1); - do_json_tok_bitcoin_amount("0.00000007", 7); - do_json_tok_bitcoin_amount("0.00000008", 8); - do_json_tok_bitcoin_amount("0.00000010", 10); - do_json_tok_bitcoin_amount("0.12345678", 12345678); - do_json_tok_bitcoin_amount("0.01234567", 1234567); - do_json_tok_bitcoin_amount("123.45678900", 12345678900); + do_json_tok_bitcoin_amount("0", true, 0); + do_json_tok_bitcoin_amount("0.00000000", true, 0); + do_json_tok_bitcoin_amount("0.00000001", true, 1); + do_json_tok_bitcoin_amount("0.00000007", true, 7); + do_json_tok_bitcoin_amount("0.00000008", true, 8); + do_json_tok_bitcoin_amount("0.00000010", true, 10); + do_json_tok_bitcoin_amount("0.12345678", true, 12345678); + do_json_tok_bitcoin_amount("0.01234567", true, 1234567); + do_json_tok_bitcoin_amount("123.45678900", true, 12345678900); + do_json_tok_bitcoin_amount("21000000.00000000", true, 2100000000000000ULL); + + do_json_tok_bitcoin_amount("184467440737.09551615", true, UINT64_MAX); + + /* Invalid inputs */ + do_json_tok_bitcoin_amount("", false, 0); + do_json_tok_bitcoin_amount("-1", false, 0); + do_json_tok_bitcoin_amount("-0.00000001", false, 0); + do_json_tok_bitcoin_amount("+1.00000000", false, 0); + do_json_tok_bitcoin_amount("1.0000000", false, 0); /* 7 decimals */ + do_json_tok_bitcoin_amount("1.000000000", false, 0); /* 9 decimals */ + do_json_tok_bitcoin_amount("184467440738.00000000", false, 0); /* overflows u64 */ + do_json_tok_bitcoin_amount("184467440737.09551616", false, 0); /* add overflows */ + do_json_tok_bitcoin_amount("1.+0000001", false, 0); + do_json_tok_bitcoin_amount("1.-0000001", false, 0); + do_json_tok_bitcoin_amount(" 1.00000000", false, 0); return 0; }