From a936aa3e4ab413f1fc9fb51008bc4126d30171bb Mon Sep 17 00:00:00 2001 From: Rhys Weatherley Date: Mon, 6 Apr 2015 09:28:21 +1000 Subject: [PATCH] Unit tests and bug fixes for pack/unpack functions --- libraries/Crypto/BigNumberUtil.cpp | 2 +- .../TestBigNumberUtil/TestBigNumberUtil.ino | 365 ++++++++++++++++++ 2 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 libraries/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino diff --git a/libraries/Crypto/BigNumberUtil.cpp b/libraries/Crypto/BigNumberUtil.cpp index b058e45d..3a56c2d4 100644 --- a/libraries/Crypto/BigNumberUtil.cpp +++ b/libraries/Crypto/BigNumberUtil.cpp @@ -65,7 +65,7 @@ void BigNumberUtil::unpackLE(limb_t *limbs, size_t count, count *= sizeof(limb_t); if (len < count) { memcpy(limbs, bytes, len); - memset(limbs + len, 0, count - len); + memset(((uint8_t *)limbs) + len, 0, count - len); } else { memcpy(limbs, bytes, count); } diff --git a/libraries/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino b/libraries/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino new file mode 100644 index 00000000..4bef55d8 --- /dev/null +++ b/libraries/Crypto/examples/TestBigNumberUtil/TestBigNumberUtil.ino @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* +This example runs tests on the utility functions in BigNumberUtil. +*/ + +#include +#include +#include +#include + +#define NUM_SIZE_512BIT (64 / sizeof(limb_t)) +#define LIMB_BITS (sizeof(limb_t) * 8) + +// Convert a decimal string in program memory into a big number. +void fromString(limb_t *x, size_t xsize, const char *str) +{ + uint8_t ch; + size_t posn; + memset(x, 0, sizeof(limb_t) * xsize); + while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') { + if (ch >= '0' && ch <= '9') { + // Quick and simple method to multiply by 10 and add the new digit. + dlimb_t carry = ch - '0'; + for (posn = 0; posn < xsize; ++posn) { + carry += ((dlimb_t)x[posn]) * 10U; + x[posn] = (limb_t)carry; + carry >>= LIMB_BITS; + } + } + ++str; + } +} + +// Convert a decimal string in program memory into a byte array. +void bytesFromString(uint8_t *x, size_t xsize, const char *str) +{ + uint8_t ch; + size_t posn; + memset(x, 0, sizeof(limb_t) * xsize); + while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') { + if (ch >= '0' && ch <= '9') { + // Quick and simple method to multiply by 10 and add the new digit. + uint16_t carry = ch - '0'; + for (posn = 0; posn < xsize; ++posn) { + carry += ((uint16_t)x[posn]) * 10U; + x[posn] = (uint8_t)carry; + carry >>= 8; + } + } + ++str; + } +} + +// Compare two big numbers. Returns -1, 0, or 1. +int compare(const limb_t *x, size_t xsize, const limb_t *y, size_t ysize) +{ + limb_t a, b; + while (xsize > 0 || ysize > 0) { + if (xsize > ysize) { + --xsize; + a = x[xsize]; + b = 0; + } else if (ysize > xsize) { + --ysize; + a = 0; + b = y[ysize]; + } else { + --xsize; + --ysize; + a = x[xsize]; + b = y[ysize]; + } + if (a < b) + return -1; + else if (a > b) + return 1; + } + return 0; +} + +// Compare two numbers where one is a decimal string. Returns -1, 0, or 1. +int compare(const limb_t *x, size_t xsize, const char *y) +{ + limb_t val[NUM_SIZE_512BIT]; + fromString(val, NUM_SIZE_512BIT, y); + return compare(x, xsize, val, NUM_SIZE_512BIT); +} + +// Prints a number in hexadecimal. +void printNumber(const char *name, const limb_t *x, size_t xsize) +{ + static const char hexchars[] = "0123456789ABCDEF"; + Serial.print(name); + Serial.print(" = "); + for (size_t posn = 0; posn < xsize; ++posn) { + for (uint8_t bit = LIMB_BITS; bit > 0; ) { + bit -= 4; + Serial.print(hexchars[(x[xsize - 1 - posn] >> bit) & 0x0F]); + } + Serial.print(' '); + } + Serial.println(); +} + +// Standard numbers that are useful in big number tests. +char const num_0[] PROGMEM = "0"; +char const num_1[] PROGMEM = "1"; +char const num_2[] PROGMEM = "2"; +char const num_4[] PROGMEM = "4"; +char const num_5[] PROGMEM = "5"; +char const num_128[] PROGMEM = "128"; +char const num_256[] PROGMEM = "256"; +char const num_2_64_m7[] PROGMEM = "18446744073709551609"; // 2^64 - 7 +char const num_2_129_m5[] PROGMEM = "680564733841876926926749214863536422907"; // 2^129 - 5 +char const num_pi_77[] PROGMEM = "31415926535897932384626433832795028841971693993751058209749445923078164062862"; // 77 digits of pi +char const num_pi_154[] PROGMEM = "3141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481"; // 154 digits of pi +char const num_2_255_m253[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819715"; // 2^255 - 253 +char const num_2_255_m20[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819948"; // 2^255 - 20 +char const num_2_255_m19[] PROGMEM = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; // 2^255 - 19 +char const num_2_255_m19_x2[] PROGMEM = "115792089237316195423570985008687907853269984665640564039457584007913129639898"; // (2^255 - 19) * 2 +char const num_a24[] PROGMEM = "121665"; +char const num_2_512_m19[] PROGMEM = "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084077"; // (2^512 - 19) + +// Table of useful numbers. +const char * const numbers[] = { + num_0, + num_1, + num_2, + num_4, + num_5, + num_128, + num_256, + num_2_64_m7, + num_2_129_m5, + num_pi_77, + num_2_255_m253, + num_2_255_m20, + num_pi_154, + num_2_512_m19, + 0 +}; +#define numbers_count ((sizeof(numbers) / sizeof(numbers[0])) - 1) + +#define foreach_number(var) \ + const char *var = numbers[0]; \ + for (unsigned index##var = 0; index##var < numbers_count; \ + ++index##var, var = numbers[index##var]) + +// Print a decimal string from program memory. +void printProgMem(const char *str) +{ + uint8_t ch; + while ((ch = pgm_read_byte((uint8_t *)str)) != '\0') { + Serial.print((char)ch); + ++str; + } +} + +// Determine if an array consists of all zero bytes. +static bool isAllZero(const uint8_t *bytes, size_t size) +{ + while (size > 0) { + if (*bytes++ != 0) + return false; + --size; + } + return true; +} + +// Determine if an array consists of all 0xBA bytes. +static bool isAllBad(const uint8_t *bytes, size_t size) +{ + while (size > 0) { + if (*bytes++ != 0xBA) + return false; + --size; + } + return true; +} + +#if !BIGNUMBER_LIMB_8BIT + +static bool isAllBad(const limb_t *limbs, size_t size) +{ + return isAllBad((uint8_t *)limbs, size * sizeof(limb_t)); +} + +#endif + +// Truncate the limb representation of a number to a specific byte length. +static void truncateNumber(limb_t *limbs, size_t bytes) +{ + size_t posn = NUM_SIZE_512BIT * sizeof(limb_t); + size_t posn2; + limb_t mask; + while (posn > bytes) { + --posn; + posn2 = posn % sizeof(limb_t); + if (posn2 == 0) + mask = 0; + else if (posn2 == 1) + mask = 0x000000FF; + else if (posn2 == 2) + mask = 0x0000FFFF; + else + mask = 0x00FFFFFF; + limbs[posn / sizeof(limb_t)] &= mask; + } +} + +// Test byte array pack and unpack operations. +void testPackUnpack(void) +{ + limb_t num[NUM_SIZE_512BIT]; + limb_t tnum[NUM_SIZE_512BIT]; + limb_t limbs[NUM_SIZE_512BIT]; + uint8_t bytes[64]; + uint8_t expected[64]; + size_t posn; + uint8_t temp; + + foreach_number(x) { + // What number are we on? + Serial.print("pack "); + printProgMem(x); + Serial.print(": "); + Serial.flush(); + bool ok = true; + + // Convert the number into limbs and bytes in a simple way. + fromString(num, NUM_SIZE_512BIT, x); + bytesFromString(expected, sizeof(expected), x); + + // Check packLE() and unpackLE() against the expected values. + for (posn = 0; posn < 64; ++posn) { + memset(bytes, 0xBA, sizeof(bytes)); + BigNumberUtil::packLE(bytes, posn, num, NUM_SIZE_512BIT); + if (memcmp(bytes, expected, posn) != 0) + ok = false; + if (!isAllBad(bytes + posn, sizeof(bytes) - posn)) + ok = false; + } + for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) { + BigNumberUtil::packLE(bytes, sizeof(bytes), num, posn); + if (memcmp(bytes, expected, posn * sizeof(limb_t)) != 0) + ok = false; + if (!isAllZero(bytes + posn * sizeof(limb_t), sizeof(bytes) - posn * sizeof(limb_t))) + ok = false; + } + for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) { + memset(limbs, 0xBA, sizeof(limbs)); + BigNumberUtil::unpackLE(limbs, posn, expected, sizeof(expected)); + if (memcmp(limbs, num, posn) != 0) + ok = false; + if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn)) + ok = false; + } + for (posn = 0; posn < 64; ++posn) { + memset(limbs, 0xBA, sizeof(limbs)); + BigNumberUtil::unpackLE(limbs, NUM_SIZE_512BIT, + expected, sizeof(expected) - posn); + memcpy(tnum, num, sizeof(num)); + truncateNumber(tnum, sizeof(expected) - posn); + if (memcmp(limbs, tnum, NUM_SIZE_512BIT) != 0) + ok = false; + } + for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) { + memset(limbs, 0xBA, sizeof(limbs)); + BigNumberUtil::unpackLE(limbs, posn, expected, sizeof(expected)); + if (memcmp(limbs, num, posn * sizeof(limb_t)) != 0) + ok = false; + if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn)) + ok = false; + } + + // Swap the expected byte array into big-endian order. + for (posn = 0; posn < 32; ++posn) { + temp = expected[posn]; + expected[posn] = expected[63 - posn]; + expected[63 - posn] = temp; + } + + // Check packBE() and unpackBE() against the expected values. + for (posn = 0; posn < 64; ++posn) { + memset(bytes, 0xBA, sizeof(bytes)); + BigNumberUtil::packBE(bytes, posn, num, NUM_SIZE_512BIT); + if (memcmp(bytes, expected + 64 - posn, posn) != 0) + ok = false; + if (!isAllBad(bytes + posn, sizeof(bytes) - posn)) + ok = false; + } + for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) { + BigNumberUtil::packBE(bytes, sizeof(bytes), num, posn); + if (memcmp(bytes + 64 - posn * sizeof(limb_t), + expected + 64 - posn * sizeof(limb_t), + posn * sizeof(limb_t)) != 0) + ok = false; + if (!isAllZero(bytes, sizeof(bytes) - posn * sizeof(limb_t))) + ok = false; + } + for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) { + memset(limbs, 0xBA, sizeof(limbs)); + BigNumberUtil::unpackBE(limbs, posn, expected, sizeof(expected)); + if (memcmp(limbs, num, posn) != 0) + ok = false; + if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn)) + ok = false; + } + for (posn = 0; posn < 64; ++posn) { + memset(limbs, 0xBA, sizeof(limbs)); + BigNumberUtil::unpackBE(limbs, NUM_SIZE_512BIT, + expected + posn, sizeof(expected) - posn); + memcpy(tnum, num, sizeof(num)); + truncateNumber(tnum, sizeof(expected) - posn); + if (memcmp(limbs, tnum, NUM_SIZE_512BIT) != 0) + ok = false; + } + for (posn = 0; posn < NUM_SIZE_512BIT; ++posn) { + memset(limbs, 0xBA, sizeof(limbs)); + BigNumberUtil::unpackBE(limbs, posn, expected, sizeof(expected)); + if (memcmp(limbs, num, posn * sizeof(limb_t)) != 0) + ok = false; + if (!isAllBad(limbs + posn, NUM_SIZE_512BIT - posn)) + ok = false; + } + + // Report the results. + if (ok) + Serial.println("ok"); + else + Serial.println("failed"); + } +} + +void setup() +{ + Serial.begin(9600); + + testPackUnpack(); +} + +void loop() +{ +}