/* Copyright (c) (2011,2012,2014,2015,2018-2020) Apple Inc. All rights reserved.
 *
 * corecrypto is licensed under Apple Inc.’s Internal Use License Agreement (which
 * is contained in the License.txt file distributed with corecrypto) and only to
 * people who accept that license. IMPORTANT:  Any license rights granted to you by
 * Apple Inc. (if any) are limited to internal use within your organization only on
 * devices and computers you own or control, for the sole purpose of verifying the
 * security characteristics and correct functioning of the Apple Software.  You may
 * not, directly or indirectly, redistribute the Apple Software or any portions thereof.
 */

#import "CCZKATValidation.h"
#import "ccz_unit.h"
#import "ccn_unit.h"
#import "string.h"

#import <corecrypto/ccz_priv.h>

struct test_alloc_context {
    unsigned test_allocs, test_frees, test_reallocs;
} gtac;

static void *
test_ccz_alloc(void *ctx, size_t size) {
    struct test_alloc_context *tac = ctx;
    tac->test_allocs++;
    return malloc(size);
}

static void
test_ccz_free(void *ctx, size_t oldsize, void *p) {
    struct test_alloc_context *tac = ctx;
    tac->test_frees++;
    cc_clear(oldsize, p);
    free(p);
}

static void *
test_ccz_realloc(void *ctx, size_t oldsize,
                 void *p, size_t newsize) {
    struct test_alloc_context *tac = ctx;
    tac->test_reallocs++;
    void *r = malloc(newsize);
    memcpy(r, p, oldsize);
    cc_clear(oldsize, p);
    free(p);
    return r;
}

struct ccz_class test_ccz_isa = {
	.ctx = &gtac,
	.ccz_alloc = test_ccz_alloc,
	.ccz_realloc = test_ccz_realloc,
	.ccz_free = test_ccz_free
};

static const uint8_t abytes[192/8] = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x20,0x21,0x22,0x23,0x24 };
static cc_unit a192[ccn_nof(192)] = {
    CCN192_C(01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)};
static cc_unit b192[ccn_nof(192)] = {
    CCN192_C(24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,09,08,07,06,05,04,03,02,01)};
static cc_unit c192[ccn_nof(192)] = {
    CCN192_C(00,00,00,00,00,00,17,16,15,14,13,12,11,10,09,00,00,00,00,00,00,00,00,00)};

static cc_unit lsr192a[ccn_nof(192)] = {
    CCN192_C(00,81,01,82,02,83,03,84,04,88,08,89,09,8A,0A,8B,0B,8C,0C,90,10,91,11,92)};
static cc_unit lsr192a8[ccn_nof(192)] = {
    CCN192_C(00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23)};
static cc_unit lsr192a64[ccn_nof(192)] = {
    CCN192_C(00,00,00,00,00,00,00,00,01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16)};
static cc_unit lsr192a65[ccn_nof(192)] = {
    CCN192_C(00,00,00,00,00,00,00,00,00,81,01,82,02,83,03,84,04,88,08,89,09,8A,0A,8B)};
static cc_unit lsl192a[ccn_nof(192)] = {
    CCN192_C(02,04,06,08,0A,0C,0E,10,12,20,22,24,26,28,2A,2C,2E,30,32,40,42,44,46,48)};
static cc_unit lsl256a64[ccn_nof(256)] = {
    CCN256_C(01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,00,00,00,00,00,00,00,00)};
static cc_unit lsl256a65[ccn_nof(256)] = {
    CCN256_C(02,04,06,08,0A,0C,0E,10,12,20,22,24,26,28,2A,2C,2E,30,32,40,42,44,46,48,00,00,00,00,00,00,00,00)};
static cc_unit sum192ab[ccn_nof(192)] = {
    CCN192_C(25,25,25,25,25,1f,1f,1f,1f,25,25,25,25,25,25,1f,1f,1f,1f,25,25,25,25,25)};
static cc_unit diff192ba[ccn_nof(192)] = {
    CCN192_C(23,21,1f,1d,1b,13,11,0f,0d,05,03,00,fe,fc,fa,f2,f0,ee,ec,e4,e2,e0,de,dd)};
static cc_unit prod384ab[ccn_nof(384)] = {
    CCN384_C(00,24,6b,d5,60,0a,ce,aa,9d,a7,9e,a3,b5,d3,fd,0c,1e,32,47,5d,4a,2f,0a,dc,9d,d7,05,29,44,58,43,2e,1a,07,f8,cf,b1,9f,9a,a4,9b,a8,cd,09,5e,d4,6b,24)};
static cc_unit square384a[ccn_nof(384)] = {
    CCN384_C(00,01,04,0a,14,23,38,54,78,a5,e9,43,b6,41,e7,a8,85,7f,97,f3,7b,30,13,25,67,90,9f,93,6b,6e,53,18,be,40,e5,5a,9f,b3,95,68,06,6f,a2,9c,a2,62,dd,10)};

@implementation CCZKATValidation

- (void) setUp {
    a = malloc(ccz_size(&test_ccz_isa));
    b = malloc(ccz_size(&test_ccz_isa));
    c = malloc(ccz_size(&test_ccz_isa));
    d = malloc(ccz_size(&test_ccz_isa));
    r = malloc(ccz_size(&test_ccz_isa));

    ccz_init(&test_ccz_isa, a);
    ccz_init(&test_ccz_isa, b);
    ccz_init(&test_ccz_isa, c);
    ccz_init(&test_ccz_isa, d);
    ccz_init(&test_ccz_isa, r);

    a_c.n = ccn_n(ccn_nof(192), a192);
    a_c.u = a192;
    b_c.n = ccn_n(ccn_nof(192), b192);
    b_c.u = b192;
    c_c.n = ccn_n(ccn_nof(192), c192);
    c_c.u = c192;
    lsra_c.n = ccn_n(ccn_nof(192), lsr192a);
    lsra_c.u  = lsr192a;
    lsra8_c.n = ccn_n(ccn_nof(192), lsr192a8);
    lsra8_c.u  = lsr192a8;
    lsra64_c.n = ccn_n(ccn_nof(192), lsr192a64);
    lsra64_c.u  = lsr192a64;
    lsra65_c.n = ccn_n(ccn_nof(192), lsr192a65);
    lsra65_c.u  = lsr192a65;
    lsla_c.n = ccn_n(ccn_nof(192), lsl192a);
    lsla_c.u  = lsl192a;
    lsla64_c.n = ccn_n(ccn_nof(256), lsl256a64);
    lsla64_c.u  = lsl256a64;
    lsla65_c.n = ccn_n(ccn_nof(256), lsl256a65);
    lsla65_c.u  = lsl256a65;
    sumab_c.n = ccn_n(ccn_nof(192), sum192ab);
    sumab_c.u  = sum192ab;
    diffba_c.n = ccn_n(ccn_nof(192), diff192ba);
    diffba_c.u  = diff192ba;
    prodab_c.n = ccn_n(ccn_nof(384), prod384ab);
    prodab_c.u  = prod384ab;
    squarea_c.n = ccn_n(ccn_nof(384), square384a);
    squarea_c.u  = square384a;
    cc_clear(sizeof(gtac), &gtac);
    expected_reallocs = 0;
}

- (void) tearDown {
    ccz_free(r);
    ccz_free(d);
    ccz_free(c);
    ccz_free(b);
    ccz_free(a);

    free(r);
    free(d);
    free(c);
    free(b);
    free(a);

    XCTAssertEqual(gtac.test_allocs, gtac.test_frees, @"allocs == frees");
    XCTAssertEqual(gtac.test_reallocs, expected_reallocs, @"reallocs == 0");
}

- (void)test_bit {
    XCTAssertEqual(ccz_bit(&a_c, 0), (bool)0, @"bit 0 of a192 is 0.");
    XCTAssertEqual(ccz_bit(&a_c, 2), (bool)1, @"bit 2 of a192 is 1.");
    XCTAssertEqual(ccz_bit(&b_c, 0), (bool)1, @"bit 0 of b192 is 1.");
    XCTAssertEqual(ccz_bit(&b_c, 191), (bool)0, @"bit 191 of b192 is 0.");
    XCTAssertEqual(ccz_bit(&b_c, 189), (bool)1, @"bit 189 of b192 is 1.");
}

- (void)test_set_bit {
    ccz_zero(a);
    for (size_t bit = 0; bit < 128; ++bit) {
        XCTAssertEqual(ccz_bit(a, bit), (bool)0, @"bit %zu of a is 0.", bit);
        ccz_set_bit(a, bit, 1);
        XCTAssertEqual(ccz_bit(a, bit), (bool)1, @"bit %zu of a is now 1.", bit);
        ccz_set_bit(a, bit, 0);
        XCTAssertEqual(ccz_bit(a, bit), (bool)0, @"bit %zu of a is now 0 again.", bit);
    }
}

- (void)test_lsr {
    ccz_lsr(r, &a_c, 1);
    XCAssertCCZEquals(r, &lsra_c, @"ccz_lsr(r, a_c, 1) works");
    ccz_set(r, &a_c);
    ccz_lsr(r, r, 1);
    XCAssertCCZEquals(r, &lsra_c, @"ccz_lsr(r, r, 1) works");

    ccz_lsr(r, &a_c, 8);
    XCAssertCCZEquals(r, &lsra8_c, @"ccz_lsr(r, a_c, 8) works");
    ccz_set(r, &a_c);
    ccz_lsr(r, r, 8);
    XCAssertCCZEquals(r, &lsra8_c, @"ccz_lsr(r, r, 8) works");

    ccz_lsr(r, &a_c, 64);
    XCAssertCCZEquals(r, &lsra64_c, @"ccz_lsr(r, a_c, 64) works");
    ccz_set(r, &a_c);
    ccz_lsr(r, r, 64);
    XCAssertCCZEquals(r, &lsra64_c, @"ccz_lsr(r, r, 64) works");

    ccz_lsr(r, &a_c, 65);
    XCAssertCCZEquals(r, &lsra65_c, @"ccz_lsr(r, a_c, 65) works");
    ccz_set(r, &a_c);
    ccz_lsr(r, r, 65);
    XCAssertCCZEquals(r, &lsra65_c, @"ccz_lsr(r, r, 65) works");
}

- (void)test_lsl {
    ccz_lsl(r, &a_c, 1);
    XCAssertCCZEquals(r, &lsla_c, @"ccz_lsl(r, a_c, 1) works");
    ccz_set(r, &a_c);
    ccz_lsl(r, r, 1);
    XCAssertCCZEquals(r, &lsla_c, @"ccz_lsl(r, r, 1) works");

    ccz_lsl(r, &a_c, 64);
    XCAssertCCZEquals(r, &lsla64_c, @"ccz_lsl(r, a_c, 64) works");
    ccz_set(r, &a_c);
    ccz_lsl(r, r, 64);
    XCAssertCCZEquals(r, &lsla64_c, @"ccz_lsl(r, r, 64) works");

    ccz_lsl(r, &a_c, 65);
    XCAssertCCZEquals(r, &lsla65_c, @"ccz_lsl(r, a_c, 65) works");
    ccz_set(r, &a_c);
    ccz_lsl(r, r, 65);
    XCAssertCCZEquals(r, &lsla65_c, @"ccz_lsl(r, r, 65) works");
}


- (void)test_bitlen {
    XCTAssertEqual(ccz_bitlen(&a_c), (size_t)185, @"ccz_bitlen(r, a_c) works");
    XCTAssertEqual(ccz_bitlen(&b_c), (size_t)190, @"ccz_bitlen(r, b_c) works");
    XCTAssertEqual(ccz_bitlen(&c_c), (size_t)141, @"ccz_bitlen(r, c_c) works");
}

- (void)test_trailing_zeros {
    XCTAssertEqual(ccz_trailing_zeros(&a_c), (size_t)2, @"ccz_trailing_zeros(r, a_c) works");
    XCTAssertEqual(ccz_trailing_zeros(&b_c), (size_t)0, @"ccz_trailing_zeros(r, b_c) works");
    XCTAssertEqual(ccz_trailing_zeros(&c_c), (size_t)72, @"ccz_trailing_zeros(r, c_c) works");
}

- (void)test_is_zero {
    ccz_zero(r);
    XCTAssertEqual(ccz_is_zero(&a_c), (bool)false, @"ccz_is_zero(r, a_c) works");
    XCTAssertEqual(ccz_is_zero(r), (bool)true, @"ccz_is_zero(r, 0) works");
}

- (void)test_is_one {
    ccz_zero(r);
    XCTAssertEqual(ccz_is_one(&a_c), (bool)false, @"ccz_is_one(r, a_c) works");
    XCTAssertEqual(ccz_is_one(r), (bool)false, @"ccz_is_one(r, 0) works");
    ccz_seti(r, 1);
    XCTAssertEqual(ccz_is_one(r), (bool)true, @"ccz_is_one(r, 1) works");
}

- (void)test_basic {
    ccz_zero(a);
    XCTAssertEqual(ccz_is_zero(a), (bool)true, @"freshly initialized ccz is_zero");
    ccz_seti(b, 42);
    XCTAssertEqual(ccz_is_zero(b), (bool)false, @"42 not is_zero");

    XCTAssertEqual(ccz_is_negative(a), (bool)false, @"freshly initialized ccz is not negative");
    ccz_seti(c, 5047);
    ccz_neg(c);
    XCTAssertEqual(ccz_is_negative(c), (bool)true, @"-5047 is negative");

    XCTAssertEqual(ccz_is_one(a), (bool)false, @"freshly initialized ccz not is_one");
    XCTAssertEqual(ccz_is_one(b), (bool)false, @"42 not is_one");
    XCTAssertEqual(ccz_is_one(c), (bool)false, @"-5047 not is_one");
    ccz_seti(d, 1);
    XCTAssertEqual(ccz_is_one(d), (bool)true, @"1 is_one");
}

- (void)test_cmp {
    XCTAssertEqual(ccz_cmp(&a_c, &a_c), 0, @"ccz_cmp(r,a_c,a_c)");
    XCTAssertEqual(ccz_cmp(&a_c, &b_c), -1, @"ccz_cmp(r,a_c,b_c)");
    XCTAssertEqual(ccz_cmp(&b_c, &a_c), 1, @"ccz_cmp(r,b_c,a_c)");
}

// All code under test must be linked into the Unit Test bundle
- (void)test_sub {
    ccz_sub(r, &b_c, &a_c);
    XCAssertCCZEquals(r, &diffba_c, @"ccz_sub(r,b_c,a_c) works");

    ccz_sub(r, &a_c, &b_c);
    XCTAssertEqual(ccz_is_negative(r), (bool)true, @"a - b is negative");

    ccz_add(r, r, &b_c);
    XCAssertCCZEquals(r, &a_c, @"a_c - b_c + b_c = a_c");

    ccz_set(r, &b_c);
    ccz_sub(r, r, &a_c);
    XCAssertCCZEquals(r, &diffba_c, @"ccz_sub(r,r,a_c) works");

    ccz_set(r, &a_c);
    ccz_sub(r, &b_c, r);
    XCAssertCCZEquals(r, &diffba_c, @"ccz_sub(r,b_c,r) works");

    ccz_set(r, &a_c);
    ccz_sub(r, r, r);
    XCTAssertTrue(ccz_is_zero(r), @"after ccz_sub(r,r,r) r = 0");
}

#if 0
- (void)test_sub1 {
    cc_unit r[ccz_nof(_c)] = {},
            s[ccz_nof(_c)] = {},
            t[ccz_nof(_c)] = {};
    s[0] = a_c[0];
    XCAssertEquals(ccz_sub(ccz_nof(_c), t, r, s), (cc_unit)1,
                   @"r = 0, ccz_sub(r,r,a_c[0]) borrow");
    XCAssertEquals(ccz_sub1(r, r, &a_c[0]), (cc_unit)1,
                   @"r = 0, ccz_sub1(r,r,a_c[0]) borrow");
    XCAssertCCZEquals(r, t, @"ccz_sub(r,b_c,r) works");
    XCAssertEquals(ccz_sub1(r, &b_c, &a_c[0]), (cc_unit)0,
                   @"ccz_sub1(r,b_c,a_c[0]) no borrow");
}
#endif

// All code under test must be linked into the Unit Test bundle
- (void)test_add {
    ccz_add(r, &a_c, &b_c);
    XCAssertCCZEquals(r, &sumab_c, @"ccz_add(r,a,b) works");

    ccz_set(r, &a_c);
    ccz_add(r, r, &b_c);
    XCAssertCCZEquals(r, &sumab_c, @"ccz_add(r,r,b) works");

    ccz_set(r, &b_c);
    ccz_add(r, &a_c, r);
    XCAssertCCZEquals(r, &sumab_c, @"ccz_add(r,a,r) works");

    ccz_set(r, &b_c);
    ccz_add(r, &a_c, r);
    XCAssertCCZEquals(r, &sumab_c, @"ccz_add(r,a,r) works");

    ccz_set(r, &a_c);
    ccz_add(r, r, r);
    ccz_lsl(a, &a_c, 1);
    //XCAssertCCZEquals(a, r, @"ccz_add(r,r,r) yields same result as ccz_lsl(r, r, 1)");
}

#if 0
- (void)test_add1 {
    cc_unit r[ccz_nof(_c)] = {},
    s[ccz_nof(_c)] = {},
    t[ccz_nof(_c)] = {};
    s[0] = a_c[0];
    XCAssertEquals(ccz_add(ccz_nof(_c), t, &b_c, s), (cc_unit)0,
                   @"r = 0, ccz_add(r,r,a_c[0]) no carry");
    XCAssertEquals(ccz_add1(r, &b_c, &a_c[0]), (cc_unit)0,
                   @"r = 0, ccz_add1(r,r,a_c[0]) no carry");
    XCAssertCCZEquals(r, t, @"ccz_add(r,b_c,r) works");
    cc_unit max[1];
    max[0] = ~CC_UNIT_C(0);
    XCAssertEquals(ccz_add1(1, r, max, CC_UNIT_C(1)), (cc_unit)1,
                   @"r = 0, ccz_add1(r,~0,1) carry");
}
#endif

// All code under test must be linked into the Unit Test bundle
- (void)test_mul {
    ccz_mul(r, &a_c, &b_c);
    XCAssertCCZEquals(r, &prodab_c, @"ccz_mul(r,a_c,b_c) works");

#if 0
    ccz_div(r, r, &b_c);
    XCAssertCCZEquals(r, &a_c, @"a_c * b_c / b_c = a_c");
#endif

    ccz_set(r, &a_c);
    ccz_mul(r, r, &b_c);
    XCAssertCCZEquals(r, &prodab_c, @"r=a_c, ccz_mul(r,r,b_c) works");

    ccz_set(r, &b_c);
    ccz_mul(r, &a_c, r);
    XCAssertCCZEquals(r, &prodab_c, @"r=b_c, ccz_mul(r,a_c,r) works");

    ccz_mul(r, &a_c, &a_c);
    XCAssertCCZEquals(r, &squarea_c, @"ccz_mul(r,a_c,a_c) r = a_c^2");

    ccz_set(r, &a_c);
    ccz_mul(r, r, r);
    XCAssertCCZEquals(r, &squarea_c, @"r=a_c, ccz_mul(r,r,r) r = a_c^2");

    //making sure the swap in ccz_mul() works, when size of a and b are not equal
    ccz_readstr(a, "c968e40c5304364b057425920b18cc358f254ddb0f42f84850d6deec46006b4a692e52b7c3bddead45f77f2c1be1"
                   "c606521d8a24260429f362d65b57873dbf270e97e210b872e45e97cb4cd87977ad20491e53c48cf0e88da9a61312"
                   "675a2527c86ac537740c5e4206972f09c0f91fa1c9f14a2cf1be07e82a3b6fd58dc12c3a", 16);

    ccz_readstr(b,"d94a0fede757d782b54ddcf6fe8d714870b78b0e67a9754cb03a5cf63bbda1c71791902ea4527fb0cd76437391e542"
                  "2c704ffb6d6018261171d8cee98adcf0243f1fd520fb3761afe94a2f4d99f94", 16);
    ccz_readstr(r,"aaf42e28af692b7defeb473a1ef2d52ce90c3fa7f19e10212b22710ba3256fea16221e5a0e553c54ba5f03bff97220"
                  "c9b74904e52ec1731cec00cb569e86816e2c415828bd745ebff3674ea63b7a2207b96745689ba8ebf7cbd67a39fec5"
                  "a44c613e4ac197550b9cfeb3fcb97b274b6fbf04972627434b1ed50ec4c630d39359675a63921f9fd1fdfa6db57bd4"
                  "b13304ac0ac84d64c262d5802a1363ee1d519f88b8ca0997a77f7ece081042d88814da526c44f1323c7ac5b7eeedcc"
                  "da0e28bc65bc415bba767d34f161ab34f9788", 16);

    ccz_mul(d, a,b);
    XCAssertCCZEquals(d, r,@"ccz_mul(r,a,b) works");

    ccz_mul(d, b,a);
    XCAssertCCZEquals(d, r,@"ccz_mul(r,b,a) works");
}

- (void)test_read_uint {
    ccz_read_uint(r, 192 / 8, abytes);
    XCAssertCCZEquals(r, &a_c, @"ccz_read_uint(a)");
}

- (void)test_write_uint_size {
    XCTAssertEqual(ccz_write_uint_size(&a_c), (size_t)(192 / 8),
                   @"ccz_write_uint_size(a_c) == 192/8");
}

- (void)test_write_uint {
    size_t i_size = ccz_write_uint_size(&a_c);
    uint8_t bytes[i_size];
    ccz_write_uint(&a_c, i_size, bytes);
    XCAssertMemEquals(i_size, bytes, abytes, @"ccz_write_uint(a_c)");
}

- (void)test_write_uint_padded {
    static const uint8_t zeros[43] = {};
    size_t i_size = ccz_write_uint_size(&a_c);
    uint8_t bytes[i_size + 43];
    ccz_write_uint(&a_c, i_size + 43, bytes);
    XCAssertMemEquals(i_size, bytes + 43, abytes, @"ccz_write_uint_padded(a_c)");
    XCAssertMemEquals(43, bytes, zeros, @"ccz_write_uint_padded(a_c)");
}

- (void)test_write_int_size {
    XCTAssertEqual(ccz_write_int_size(&a_c), (size_t)(192 / 8), @"ccz_write_int_size(a_c) == 192/8");
    ccz_set(r, &a_c);
    ccz_set_bit(r, 191, 1);
    XCTAssertEqual(ccz_write_int_size(r), (size_t)(192 / 8) + 1, @"ccz_write_int_size(0x80 a_c) == 192/8 + 1");
}

- (void)test_write_int {
    size_t i_size = ccz_write_int_size(&a_c);
    uint8_t bytes[i_size];
    ccz_write_int(&a_c, i_size, bytes);
    XCAssertMemEquals(i_size, bytes, abytes, @"ccz_write_int(a_c)");

    ccz_set(r, &a_c);
    ccz_set_bit(r, 191, 1);
    size_t j_size = ccz_write_int_size(r);
    uint8_t jbytes[j_size];
    ccz_write_int(r, j_size, jbytes);
    XCAssertMemEquals(j_size - 1 - CCN_UNIT_SIZE, jbytes + 1 + CCN_UNIT_SIZE, abytes + CCN_UNIT_SIZE, @"ccz_write_int(0x80 a_c)");
    XCTAssertEqual(jbytes[0], (uint8_t)0, @"first byte is zero(a_c)");
}

- (void)test_read_radix2 {
    ccz_seti(c, 5047);

    char input_radix_sign[] = "+1001110110111";
    const char *input_radix_nosign=&input_radix_sign[1];

    // Positive
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_nosign), input_radix_nosign, 2),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix 1001110110111 base 2 is 5047");

    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_sign), input_radix_sign, 2),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix +1001110110111 base 2 is 5047");

    // Negative
    input_radix_sign[0]='-';
    ccz_neg(c);
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_sign), input_radix_sign, 2),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix -1001110110111 base 2 is -5047");
}

- (void)test_read_radix16 {
    ccz_seti(c, 5047);

    char input_radix_sign[] = "+13B7";
    const char *input_radix_nosign=&input_radix_sign[1];

    // Positive
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_nosign), input_radix_nosign, 16),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix 13B7 base 16 is 5047");
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_sign), input_radix_sign, 16),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix +13B7 base 16 is 5047");

    // Negative
    input_radix_sign[0]='-';
    ccz_neg(c);
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_sign), input_radix_sign, 16),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix -13B7 base 16 is -5047");
}

- (void)test_read_radix10 {
    ccz_seti(c, 5047);

    char input_radix_sign[] = "+5047";
    const char *input_radix_nosign=&input_radix_sign[1];

    // Positive
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_nosign), input_radix_nosign, 10),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix 5047 base 10 is 5047");
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_sign), input_radix_sign, 10),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix +5047 base 10 is 5047");

    // Negative
    ccz_neg(c);
    input_radix_sign[0]='-';
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_sign), input_radix_sign, 10),(int)0, @"ccz_read_radix return value");
    XCTAssertEqual(ccz_cmp(c,r),(int)0, @"ccz_read_radix -5047 base 10 is -5047");
}

- (void)test_read_radix_error {

    // Incorrect input
    const char *input_radix_minus = "-5047A";
    const char *input_radix_plus=&input_radix_minus[1];

    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_plus), input_radix_plus, 10),(int)CCZ_INVALID_INPUT_ERROR, @"Detect invalid input - Positive");
    ccz_neg(c);
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_minus), input_radix_minus, 10),(int)CCZ_INVALID_INPUT_ERROR, @"Detect invalid input - Negative");

    // Incorrect radix
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_plus)-1, input_radix_plus, 65),(int)CCZ_INVALID_RADIX_ERROR, @"Detect invalid radix - Too large");
    XCTAssertEqual(ccz_read_radix(r, strlen(input_radix_minus)-1, input_radix_minus, 0),(int)CCZ_INVALID_RADIX_ERROR, @"Detect invalid radix - Too zero");
}


- (void)test_write_radix16 {
    ccz_seti(c, 5047);
    ccz_neg(c);
    char buf[1024];

    size_t clen_b16 = ccz_write_radix_size(c, 16);
    XCTAssertEqual(clen_b16, (size_t)5, @"ccz_write_radix_size -5047 base 16 is 5");
    buf[0] = (char)0xde;
    buf[1 + clen_b16] = (char)0xed;
    ccz_write_radix(c, clen_b16, &buf[1], 16);
    XCTAssertEqual(buf[0], (char)0xde, @"begin marker ok");
    XCTAssertEqual(buf[1 + clen_b16], (char)0xed, @"end marker ok");
    XCAssertCharsEquals(clen_b16, &buf[1], "-13B7", @"ccz_write_radix -5047 base 16 is -13B7");
}

- (void)test_write_radix10 {
    ccz_seti(c, 5047);
    ccz_neg(c);
    char buf[1024];

    size_t clen_b10 = ccz_write_radix_size(c, 10);
    XCTAssertEqual(clen_b10, (size_t)5, @"ccz_write_radix_size -5047 base 10 is 5");
    buf[0] = (char)0xde;
    buf[1 + clen_b10] = (char)0xed;
    ccz_write_radix(c, clen_b10, &buf[1], 10);
    XCTAssertEqual(buf[0], (char)0xde, @"begin marker ok");
    XCTAssertEqual(buf[1 + clen_b10], (char)0xed, @"end marker ok");
    XCAssertCharsEquals(5, &buf[1], "-5047", @"ccz_write_radix -5047 base 10 is -5047");
}

- (void)test_write_radix2 {
    ccz_seti(c, 5047);
    ccz_neg(c);
    char buf[1024];

    size_t clen_b2 = ccz_write_radix_size(c, 2);
    XCTAssertEqual(clen_b2, (size_t)14, @"ccz_write_radix_size -5047 base 2 is 14");
    buf[0] = (char)0xde;
    buf[1 + clen_b2] = (char)0xed;
    ccz_write_radix(c, clen_b2, &buf[1], 2);
    XCTAssertEqual(buf[0], (char)0xde, @"begin marker ok");
    XCTAssertEqual(buf[1 + clen_b2], (char)0xed, @"end marker ok");
    XCAssertCharsEquals(clen_b2, &buf[1], "-1001110110111", @"ccz_write_radix -5047 base 2 is -1001110110111");
}

static inline int ccz_readstr(ccz *r, const char *str, unsigned radix) {
    return ccz_read_radix(r, strlen(str), str, radix);
}

- (void)test_mod {
    XCTAssertEqual(ccz_readstr(a, "c968e40c5304364b057425920b18cc358f254ddb0f42f84850d6deec46006b4a692e52b7c3bddead45f77f2c1be1c606521d8a24260429f362d65b57873dbf270e97e210b872e45e97cb4cd87977ad20491e53c48cf0e88da9a61312675a2527c86ac537740c5e4206972f09c0f91fa1c9f14a2cf1be07e82a3b6fd58dc12c3a", 16),(int)0, @"ccz_readstr: ccz_read_radix unexpected failure");
    XCTAssertEqual(ccz_readstr(c, "354c912b09ee7abff5b3d94ed52a9e8dcae582e094daa375c495f970710af73efcc4f9776010511f654c7408a6d5d351ab1d94a0fede757d782b54ddcf6fe8d714870b78b0e67a9754cb03a5cf63bbda1c71791902ea4527fb0cd76437391e5422c704ffb6d6018261171d8cee98adcf0243f1fd520fb3761afe94a2f4d99f94", 16),(int)0, @"ccz_readstr: ccz_read_radix unexpected failure");
    XCTAssertEqual(ccz_readstr(r, "2983308b3538c60b245899a58b98f08c2e74c53950b30de70314f29af2df858d72df6651a38ceb4f1612231227604c1150c4cc412968c97afa545cbe18ee04a1d102bfa6a5bf7498996a41e70b4c7991f3c9e87984321915b87f8ce5c1aeca2b6015b6384f8a59bae351d662f52f1634c3257434fb8eed85d93fb1ecaf344d7e", 16),(int)0, @"ccz_readstr: ccz_read_radix unexpected failure");

    ccz_mod(a, a, c);
    
    
    XCAssertCCZEquals(a, r, @"ccz_mod a = a % c");
    
    // pathological test case from failing SRP
    ccz_readstr(a, "12573135c66ab09ff6f5dfb777d18756", 16);
    ccz_readstr(b, "0000000000000010", 16);
    ccz_readstr(r, "0000000000000006", 16);
    ccz_mod(c, a, b);
    
    XCAssertCCZEquals(c, r, @"ccz_mod a = a % c");

}

- (void)test_modmul {

    ccz_readstr(a, "2983308b3538c60b245899a58b98f08c2e74c53950b30de70314f29af2df858d72df66"
                "51a38ceb4f1612231227604c1150c4cc412968c97afa545cbe18ee04a1d102bfa6a5bf"
                "7498996a41e70b4c7991f3c9e87984321915b87f8ce5c1aeca2b6015b6384f8a59bae3"
                "51d662f52f1634c3257434fb8eed85d93fb1ecaf344d7e", 16);

    ccz_readstr(b, "fb978a4f4fccdb14c7268918b784c4f6d5281c0d6ff43e60e88e97f97f2617608de248"
                "8c84eb99a3f467013c860536ec74f4968abeccbc1b026ee5873e40bdd292f8f7416a93"
                "df619288b49ba21d3e09aa796cb35a340b1abfda4e3b6cd92df2de64967e6a59f78758"
                "6929c4d2920da20caeb384594d7f2b7e999dab0d6a1ac", 16);

    ccz_readstr(c, "354c912b09ee7abff5b3d94ed52a9e8dcae582e094daa375c495f970710af73efcc4f9"
                "776010511f654c7408a6d5d351ab1d94a0fede757d782b54ddcf6fe8d714870b78b0e6"
                "7a9754cb03a5cf63bbda1c71791902ea4527fb0cd76437391e5422c704ffb6d6018261"
                "171d8cee98adcf0243f1fd520fb3761afe94a2f4d99f94", 16);

    ccz_readstr(r, "332f5fc965b485b8c960afbc26e6e278fc134163931602ae89c61246f3131cc62e711e"
                "637189a3a184eaf28d0ef89572360472f1d0ee625722606d36b031e608704b40840f17"
                "7ffc6cc392bfe628d51bd8cb5b1a6558ae5c1378f656877490d57d126629318400b866"
                "756827a525df23f931720250cddab02be65e14b624bfc4", 16);


    ccz_mulmod(d, a, b, c);

    XCAssertCCZEquals(d, r, @"ccz_mulmod r = a*b mod c");
    

    ccz_set(d,a);
    ccz_mulmod(d, d, b, c);
    XCAssertCCZEquals(d, r, @"ccz_mulmod r = a*b mod c - result in place of a");

    ccz_set(d,b);
    ccz_mulmod(d, a, d, c);
    XCAssertCCZEquals(d, r, @"ccz_mulmod r = a*b mod c - result in place of b");

}

- (void)test_expmod {
    ccz_seti(a, 2);
    ccz_seti(b, 64);
    ccz_seti(c, 0xffffffff);
    ccz_expmod(d, a, b, c);
    ccz_seti(a, 1);
    XCAssertCCZEquals(d, a, @"2**64 mod 0xffffffff = 1?");

    ccz_readstr(a, "c968e40c5304364b057425920b18cc358f254ddb0f42f84850d6deec46006b4a692e52b7c3bddead45f77f2c1be1c606521d8a24260429f362d65b57873dbf270e97e210b872e45e97cb4cd87977ad20491e53c48cf0e88da9a61312675a2527c86ac537740c5e4206972f09c0f91fa1c9f14a2cf1be07e82a3b6fd58dc12c3a", 16);
    ccz_seti(b, 65537);
    ccz_readstr(c, "354c912b09ee7abff5b3d94ed52a9e8dcae582e094daa375c495f970710af73efcc4f9776010511f654c7408a6d5d351ab1d94a0fede757d782b54ddcf6fe8d714870b78b0e67a9754cb03a5cf63bbda1c71791902ea4527fb0cd76437391e5422c704ffb6d6018261171d8cee98adcf0243f1fd520fb3761afe94a2f4d99f94", 16);
    ccz_readstr(r, "0fb978a4f4fccdb14c7268918b784c4f6d5281c0d6ff43e60e88e97f97f2617608de2488c84eb99a3f467013c860536ec74f4968abeccbc1b026ee5873e40bdd292f8f7416a93df619288b49ba21d3e09aa796cb35a340b1abfda4e3b6cd92df2de64967e6a59f787586929c4d2920da20caeb384594d7f2b7e999dab0d6a1ac", 16);
    ccz_expmod(d, a, b, c);
    XCAssertCCZEquals(d, r, @"ccz_expmod r = (a^b) mod c Test a > c");

    ccz_readstr(a, "2983308b3538c60b245899a58b98f08c2e74c53950b30de70314f29af2df858d72df6651a38ceb4f1612231227604c1150c4cc412968c97afa545cbe18ee04a1d102bfa6a5bf7498996a41e70b4c7991f3c9e87984321915b87f8ce5c1aeca2b6015b6384f8a59bae351d662f52f1634c3257434fb8eed85d93fb1ecaf344d7e", 16);
    ccz_expmod(d, a, b, c);
    XCAssertCCZEquals(d, r, @"ccz_expmod r = (a^b) mod c Test a normalized < c");
    
// #if 0
    ccz_readstr(r, "8f2fcb71936d463a70f02ef788ef27b363b3bfceb5f0692e8d3bddf74ac35c670f243d05097f99a0e8db9b29b22c6c99cfb8386d92c80c231a756bca77730baa", 16);
    ccz_readstr(a, "0000000000000002", 16);
    ccz_readstr(b, "d7ebf1988ad4ce0661ad4e8f52a7d9370fa92de13dac98ea8ccfe929bd4398ff", 16);
    ccz_readstr(c, "d4c7f8a2b32c11b8fba9581ec4ba4f1b04215642ef7355e37c0fc0443ef756ea2c6b8eeb755a1c723027663caa265ef785b8ff6a9b35227a52d86633dbdfca43", 16);
    ccz_expmod(d, a, b, c);
    XCAssertCCZEquals(d, r, @"ccz_expmod r = (a^b) mod c Test small a normalized < c");
// #endif

}

#if 0
// This works, but currently takes a long time.
-(void) test_write_radix10_10kbits {
    ccz_seti(a, 1);

    ccz_lsl(b, a, 10000);
    expected_reallocs += ccz_capacity(b) < ccn_nof(10000);

    size_t rs = ccz_write_radix_size(b, 10);
    char bbuf[rs + 1];
    bbuf[rs] = 0;
    ccz_write_radix(b, rs, bbuf, 10);
    fprintf(stderr, "%s\n", bbuf);
}
#endif

-(void) test_write_radix10_1kbits {
    ccz_seti(a, 1);
    ccz_lsl(b, a, 1000);
    const char expected_result[]="1071508607186267320948425049060001810561404811705533607443750388370351" \
    "0511249361224931983788156958581275946729175531468251871452856923140435" \
    "9845775746985748039345677748242309854210746050623711418779541821530464" \
    "7498358194126739876755916554394607706291457119647768654216766042983165" \
    "2624386837205668069376";
    expected_reallocs += ccz_capacity(b) < ccn_nof(1000);

    size_t rs = ccz_write_radix_size(b, 10);
    char bbuf[rs + 1];
    bbuf[rs] = 0;
    ccz_write_radix(b, rs, bbuf, 10);
    XCAssertCharsEquals(sizeof(expected_result), bbuf, &expected_result[0], @"ccz_write_radix 2^1000 base 10");
}

-(void) test_divmod {
    ccz_seti(a, 1);

    ccz_lsl(b, a, 10000);
    expected_reallocs += (ccz_capacity(a) < ccn_nof(10000));

    ccz_seti(c, 64);
    ccz_mul(d, b, c);
    ccz_divmod(a, b, d, c);
    XCTAssertEqual(ccz_is_zero(b), (bool)true, @"remainder is zero");
    ccz_lsr(b, a, 10000);
    ccz_seti(a, 1);
    XCAssertCCZEquals(b, a, @"((((1 << 10000) * 64) / 64) >> 10000) == 1?");
}

- (void)test_set {
    ccz_set(r, &a_c);
    XCAssertCCZEquals(r, &a_c, @"ccz_set(r,a_c) r = a_c");
}

#if 0
- (void)test_seti {
    cc_unit r[ccz_nof(_c)];
    ccz_seti(r, &a_c[0]);
    cc_unit s[ccz_nof(_c)] = {};
    s[0] = a_c[0];
    XCAssertCCZEquals(r, s, @"ccz_seti(r,a_c) r = a_c");
}
#endif

@end
