Skip to content

Commit

Permalink
feat(crypto): add thousands separator to bn_format
Browse files Browse the repository at this point in the history
  • Loading branch information
matejcik committed Aug 2, 2022
1 parent 7f990e3 commit f0976fe
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 63 deletions.
2 changes: 1 addition & 1 deletion crypto/address.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ void ethereum_address_checksum(const uint8_t *addr, char *address, bool rskip60,
keccak_256_Init(&ctx);
if (rskip60) {
char prefix[16] = {0};
int prefix_size = bn_format_uint64(chain_id, NULL, "0x", 0, 0, false,
int prefix_size = bn_format_uint64(chain_id, NULL, "0x", 0, 0, false, 0,
prefix, sizeof(prefix));
keccak_Update(&ctx, (const uint8_t *)prefix, prefix_size);
}
Expand Down
31 changes: 23 additions & 8 deletions crypto/bignum.c
Original file line number Diff line number Diff line change
Expand Up @@ -1670,27 +1670,31 @@ void bn_divmod10(bignum256 *x, uint32_t *r) { bn_long_division(x, 10, x, r); }
// Assumes output is an array of length output_length
// The function doesn't have neither constant control flow nor constant memory
// access flow with regard to any its argument
size_t bn_format(const bignum256 *amount, const char *prefix, const char *suffix, unsigned int decimals, int exponent, bool trailing, char *output, size_t output_length) {
size_t bn_format(const bignum256 *amount, const char *prefix, const char *suffix, unsigned int decimals, int exponent, bool trailing, char thousands, char *output, size_t output_length) {

/*
Python prototype of the function:
def format(amount, prefix, suffix, decimals, exponent, trailing):
def format(amount, prefix, suffix, decimals, exponent, trailing, thousands):
if exponent >= 0:
amount *= 10 ** exponent
amount *= 10**exponent
else:
amount //= 10 ** (-exponent)
d = pow(10, decimals)
integer_part = amount // d
integer_str = f"{integer_part:,}".replace(",", thousands or "")
if decimals:
output = "%d.%0*d" % (amount // d, decimals, amount % d)
decimal_part = amount % d
decimal_str = f".{decimal_part:0{decimals}d}"
if not trailing:
output = output.rstrip("0").rstrip(".")
decimal_str = decimal_str.rstrip("0").rstrip(".")
else:
output = "%d" % (amount // d)
decimal_str = ""
return prefix + output + suffix
return prefix + integer_str + decimal_str + suffix
*/

// Auxiliary macro for bn_format
Expand Down Expand Up @@ -1773,18 +1777,29 @@ size_t bn_format(const bignum256 *amount, const char *prefix, const char *suffix

{ // Add integer-part digits of amount
// Add trailing zeroes
int digits = 0;
if (!bn_is_zero(&temp)) {
for (; exponent > 0; --exponent) {
++digits;
BN_FORMAT_ADD_OUTPUT_CHAR('0')
if (thousands != 0 && digits % 3 == 0) {
BN_FORMAT_ADD_OUTPUT_CHAR(thousands)
}
}
}
// decimals == 0 && exponent == 0

// Add significant digits
bool is_zero = false;
do {
++digits;
bn_divmod10(&temp, &digit);
is_zero = bn_is_zero(&temp);
BN_FORMAT_ADD_OUTPUT_CHAR('0' + digit)
} while (!bn_is_zero(&temp));
if (thousands != 0 && !is_zero && digits % 3 == 0) {
BN_FORMAT_ADD_OUTPUT_CHAR(thousands)
}
} while (!is_zero);
}

// Add prefix
Expand Down
15 changes: 12 additions & 3 deletions crypto/bignum.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ void bn_divmod1000(bignum256 *x, uint32_t *r);
void bn_inverse(bignum256 *x, const bignum256 *prime);
size_t bn_format(const bignum256 *amount, const char *prefix,
const char *suffix, unsigned int decimals, int exponent,
bool trailing, char *output, size_t output_length);
bool trailing, char thousands, char *output,
size_t output_length);

// Returns (uint32_t) in_number
// Assumes in_number < 2**32
Expand Down Expand Up @@ -149,13 +150,21 @@ static inline int bn_is_odd(const bignum256 *x) { return (x->val[0] & 1) == 1; }

static inline size_t bn_format_uint64(uint64_t amount, const char *prefix,
const char *suffix, unsigned int decimals,
int exponent, bool trailing, char *output,
int exponent, bool trailing,
char thousands, char *output,
size_t output_length) {
bignum256 bn_amount;
bn_read_uint64(amount, &bn_amount);

return bn_format(&bn_amount, prefix, suffix, decimals, exponent, trailing,
output, output_length);
thousands, output, output_length);
}

static inline size_t bn_format_amount(uint64_t amount, const char *prefix,
const char *suffix, unsigned int decimals,
char *output, size_t output_length) {
return bn_format_uint64(amount, prefix, suffix, decimals, 0, false, ',',
output, output_length);
}

#if USE_BN_PRINT
Expand Down
2 changes: 1 addition & 1 deletion crypto/fuzzer/fuzzer.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ int fuzz_bn_format(void) {
}

ret = bn_format(&target_bignum, prefix, suffix, decimals, exponent, trailing,
buf, FUZZ_BN_FORMAT_OUTPUT_BUFFER_SIZE);
0, buf, FUZZ_BN_FORMAT_OUTPUT_BUFFER_SIZE);

// basic sanity checks for r
if (ret > FUZZ_BN_FORMAT_OUTPUT_BUFFER_SIZE) {
Expand Down
2 changes: 1 addition & 1 deletion crypto/nem.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ static inline bool nem_write_mosaic_u64(nem_transaction_ctx *ctx,
const char *name, uint64_t value) {
char buffer[21] = {0};

if (bn_format_uint64(value, NULL, NULL, 0, 0, false, buffer,
if (bn_format_uint64(value, NULL, NULL, 0, 0, false, 0, buffer,
sizeof(buffer)) == 0) {
return false;
}
Expand Down
29 changes: 18 additions & 11 deletions crypto/tests/test_bignum.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import random
from ctypes import (
c_bool,
c_char,
c_int,
c_size_t,
c_uint,
Expand Down Expand Up @@ -607,23 +608,27 @@ def assert_bn_divmod10(x_old):
assert r == x_old % 10


def assert_bn_format(x, prefix, suffix, decimals, exponent, trailing):
def format(amount, prefix, suffix, decimals, exponent, trailing):
def assert_bn_format(x, prefix, suffix, decimals, exponent, trailing, thousands):
def format(amount, prefix, suffix, decimals, exponent, trailing, thousands):
if exponent >= 0:
amount *= 10**exponent
else:
amount //= 10 ** (-exponent)

d = pow(10, decimals)

integer_part = amount // d
integer_str = f"{integer_part:,}".replace(",", thousands or "")

if decimals:
output = "%d.%0*d" % (amount // d, decimals, amount % d)
decimal_part = amount % d
decimal_str = f".{decimal_part:0{decimals}d}"
if not trailing:
output = output.rstrip("0").rstrip(".")
decimal_str = decimal_str.rstrip("0").rstrip(".")
else:
output = "%d" % (amount // d)
decimal_str = ""

return prefix + output + suffix
return prefix + integer_str + decimal_str + suffix

def string_to_char_p(string):
return ctypes.create_string_buffer(string.encode("ascii"))
Expand All @@ -641,11 +646,12 @@ def char_p_to_string(pointer):
c_uint(decimals),
c_int(exponent),
c_bool(trailing),
c_char(0),
output,
c_size_t(output_length),
)

correct_output = format(x, prefix, suffix, decimals, exponent, trailing)
correct_output = format(x, prefix, suffix, decimals, exponent, trailing, "")
correct_return_value = len(correct_output)
if len(correct_output) >= output_length:
correct_output = ""
Expand Down Expand Up @@ -1018,15 +1024,16 @@ def test_bn_divmod10(r):


@pytest.mark.parametrize(
"decimals,exponent,trailing,prefix,suffix,value",
"decimals,exponent,trailing,prefix,suffix,thousands,value",
itertools.product(
range(0, 5),
range(-5, 5),
[True, False],
["", "prefix"],
["", "suffix"],
[123, 120],
["", ",", " "],
[123, 120, 123_456, 12_345, 100001, 10001000, -10001000],
),
)
def test_bn_format(decimals, exponent, trailing, prefix, suffix, value):
assert_bn_format(value, prefix, suffix, decimals, exponent, trailing)
def test_bn_format(decimals, exponent, trailing, prefix, suffix, thousands, value):
assert_bn_format(value, prefix, suffix, decimals, exponent, trailing, thousands)
Loading

0 comments on commit f0976fe

Please sign in to comment.