/* $Id: t_dibigdRSAEncrypt.cpp $ */

/*
* Copyright (C) 2026 David Ireland, D.I. Management Services Pty Limited
* <https://di-mgt.com.au/contact/> <https://di-mgt.com.au/bigdigits.html>
* SPDX-License-Identifier: MPL-2.0
*
* Last updated:
* $Date: 2026-05-05 02:02 $
* $Revision: 1.0.1 $
* $Author: dai $
*/

/* Encrypt data using RSA PKCS#1 v1.5 standard */

#ifdef NDEBUG
#undef NDEBUG
#endif
#include <iostream>
#include <string>
#include <iomanip>
#include <stdexcept>
#include <vector>
#include <assert.h>
#include <time.h>
#include "dibigd.hpp"
using std::cout;
using std::endl;
using namespace dibigd;

/* Hard code the RSA key values */

// Bob's public encryption key from RFC9216
BigDigit n = 
    "0xE67005D20951A1FF4D8C12A47BA83E"
    "ED12EB3A045FC108DC1FEDB39B401FEB"
    "B14944DAC4C13B8EBAD9566A6500DE9F"
    "7EC1BED7AA2CD51CE42155EB23C20A7C"
    "3C9A68F00766F1AAFCE1DDADB5ECB539"
    "577168226E35EED22637EBC12841E8DB"
    "C09BB4BC0831B7F846A990EF5FCE7DA9"
    "5B0691110EADC16B478B5413E753DDB9"
    "1B62435A2AB2E264F0B04381431994A5"
    "2C6977C5F062B32651AE68B465E7FDCA"
    "7EBF7E6FA564083BB14D960B671B7AF9"
    "ADFF424682DC8F66DB280FB9BB471490"
    "793F9FACF1BA113922A6D4E07582C2E8"
    "C3C785F52ECEB5C1867A8A1926225440"
    "2475E0D694E5B2327CA119576612B57C"
    "1DCEDA04249D9F4544482BEF189F0941"
    "DD";
BigDigit e(0x10001);
// Bob's private key
BigDigit d = 
    "0x05C420D5F4ED89E67E3BF68D754D78F4"
    "D2B4AB1F7B18B4C18C882E4040C30551"
    "4ADA5BB83E1060F40760014A8B1F53C4"
    "F9EABAE49686670BC51FC5F532CD802A"
    "247362C3CE408A09E27D6173AF6E8843"
    "7D62472FE101CEBEFAAD0AD5988C9C9B"
    "E41CBD58F78B1FA4329C9E85941A12AB"
    "18544525B261F4EEC2AB75387121B14B"
    "877773790666C589104D8F868803DCF3"
    "FA2E712F49B7DE430A6A2FC08E3DEC23"
    "4009B19E595408586C2F2C0ADD577AF6"
    "FA8B8D69F52E864B0182D7D6F2FB5CBD"
    "582D501AD6F9183A2DBC284CBE6652FC"
    "EDCD78373AC42FCB66841C1CD5608997"
    "7B7F583A88490994260AF83EF1B1DED8"
    "EB7D30EF91DA8F4BE796188939B480FB";
BigDigit p =
    "0xF1BFC8E9D83E885511A061F6829D45"
    "37320EF4C1A817D5F80F1CEF5293A7BD"
    "73C0A0391876BB05A3A536B0BBCF3104"
    "617267EC675AAAFB18236C96D6ACDE6F"
    "78AE2E00E11F78FB9B8BF3EA0E2C2DBA"
    "80C0DFCC04DDD50DE4FE1F75F582E4F3"
    "DB689D01146C215925DA614E111BBE0D"
    "96FA736C1157101AE0A5CE15F403218C"
    "B3";
BigDigit q = 
    "0xF4058937B1D1906C103F8580F5118B"
    "197D879B52262FDA54ED5464604E884A"
    "15C9510163AD18FF5845B2B21159012E"
    "18E0917CD2F711AA2B2C0FEF5CFD227C"
    "BECC319BCE4F04941FE0952EC61465F9"
    "D131B42127DA1564D8771B7898DF6976"
    "E644D9B953FF6EAEC94F035C2101845F"
    "CEAA1C108C9A8A4D84D3465DC0ADAE5F"
    "2F";
BigDigit dP = 
    "0xD0C95B632320F6ABC9DA6360FC7389"
    "A8DEF1EDCF2736D6D4337140FC678BD2"
    "629B3585BA2C28DFF8F2A26646FDED8E"
    "A3FEA3E4976AC60AB0513FE2922BA0BC"
    "5354D3646D09BA7A4B5FB6DC293B5C8F"
    "1BCDF83B02E4F7B1D558E8A0FED5FA98"
    "E5A0D7206C9BEABD4CA4EBAEF9B5A511"
    "A5984E81420498632E2BE4EE2EDD5967"
    "63";
BigDigit dQ = 
    "0xA507816F5B20E14D5407C1A01F1D48"
    "376AFFE4F93FBE29283A815289175370"
    "D71F195B992F9DB242DE90258139F264"
    "5C8AB399F98B023555912D0DB293B759"
    "2A282A7CB6A015F69F4061640AAE0CF1"
    "509B0EB9459C65CF97DDA5847FCEC822"
    "934489029F5E265AAAE676DD3DAD2AFB"
    "28673F27AA0F71ED4F211B7B982D5DBA"
    "1D";
BigDigit qInv = 
    "0x2389FC2DB5C96DB93706791547613EC4"
    "26547EFE63274968BCC74BF73B3EB3A0"
    "40544C75E8EF024BBF79825B29B52FEE"
    "AB091481B88BF206D027D59AEFC16B1E"
    "5F6DF619B6807FD33C5668B908112B55"
    "59A2AB48A45D4EB3734C03AB79E24AB1"
    "8E11369EABBE6A60A236E62060354E0F"
    "0CBA9D3143C178776F2BB547B29C7C73";


void do_test() {
    cout << "Encrypt data using RSA PKCS#1 v1.5 standard." << endl;
    cout << "============================================" << endl;
    
    std::string msg ="abc";    /* Default message */
    size_t msglen = msg.length();

    // We will encrypt a message msg and send to Bob, encrypting using Bob's public key.
    cout << "Encrypting a message to Bob..." << endl;
    cout << "Message to encrypt is '" << msg << "' " << msglen << " bytes long." << endl;
        size_t nbits = n.bitlen();
    cout << "Bob's public key is " << nbits << " bits long" << endl;

    /* Create a PKCS#1 v1.5 EME message block in octet format */
    /*
    |<-----------------(klen bytes)--------------->|
    +--+--+-------+--+-----------------------------+
    |00|02|PADDING|00|      DATA TO ENCRYPT        |
    +--+--+-------+--+-----------------------------+
    The padding is made up of _at least_ eight non-zero random bytes.
    */
    /* How big is the key in octets (8-bit bytes)? */
    size_t klen = (nbits + 7) / 8;
    /* Set up encryption block of klen bytes */
    std::vector<unsigned char> block(klen);
    /* Set fixed byte values */
    block.at(0) = 0x00;
    block.at(1) = 0x02;
    block.at(klen - msglen - 1) = 0x00;
    // Length of random padding string
    size_t padlen = klen - msglen - 3;
    assert(padlen >= 8);
    // Fill with nonzero random bytes 0 < r < 256
    BigDigit r;
    for (size_t i = 0; i < padlen; i++) {
        while (r.set_rand_number(256) == 0) {
            ;
        }
        block.at(i + 2) = (unsigned char)(r.to_short() & 0xff);
    }
    // Copy the raw bytes from the msg string (yuk!)
    unsigned char* raw = reinterpret_cast<unsigned char*>(const_cast<char*>(msg.c_str()));
    for (size_t i = 0; i < msglen; i++) {
        block.at(padlen + 3 + i) = raw[i];
    }
    Bvec::print_hex("block, m=\n", block);

    /* Convert block byte array to a big integer m */
    BigDigit m = from_octets(block);

    /* Encrypt c = m^e mod n */
    BigDigit c = m.mod_exp(e, n);
    c.printhex("c=");

    /* Check decrypt m1 = c^d mod n */
    cout << "About to decrypt..." << endl;
    clock_t start = clock();
    BigDigit m1 = c.mod_exp(d, n);
    clock_t finish = clock();
    m1.printhex("m'=");
    bool ok = (m1 == m);
    cout << "Decryption " << (ok ? "OK" : "FAILED!") << endl;
    assert(m1 == m);
    double interval = (double)(finish - start) / CLOCKS_PER_SEC;
    cout << "Decryption by inversion took " << interval << " seconds" << endl;

    /* Extract the message bytes from the decrypted block, exactly klen bytes */
    block = m1.to_octets(klen);
    Bvec::print_hex(block);
    assert(block.at(0) == 0x00);
    assert(block.at(1) == 0x02);
    size_t i;
    for (i = 2; i < block.size(); i++) {
        // Look for zero separating byte
        if (block.at(i) == 0x00)
            break;
    }
    if (i < klen) {
        cout << "Decrypted message is '";
        size_t nchars = klen - i - 1;
        for (size_t j = 0; j < nchars; j++) {
            cout << block.at(i + 1 + j);
        }
        cout << "'" << endl;
    }
    else {
        cout << "ERROR: failed to find message in decrypted block" << endl;
    }

    /* Decrypt using CRT method - Ref: PKCS #1/RFC8017 */
    cout << "Decrypting by CRT method..." << endl;
    start = clock();
    /* Let m_1 = c^dP mod p. */
    BigDigit m_1 = c.mod_exp(dP, p);
    /* Let m_2 = c^dQ mod q. */
    BigDigit m_2 = c.mod_exp(dQ, q);
    /* Let h = qInv ( m_1 - m_2 ) mod p. */
    BigDigit h = qInv.mod_mult(m_1.mod_sub(m_2, p), p);
    /* Let m = m_2 + hq. */
    m1 = m_2 + h * q;
    m1.printhex("m'=");
    finish = clock();
    assert(m1 == m);
    interval = (double)(finish - start) / CLOCKS_PER_SEC;
    cout << "Decryption by CRT took " << interval << " seconds" << endl;
}

int main() {
    /* MSVC memory leak checking stuff */
#if _MSC_VER >= 1100
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
#endif

    /* Catch any exceptions */
    try {
        do_test();
    }
    catch (const std::exception& e) {
        // Handle standard exceptions with a specific message
        std::cerr << "Standard exception: " << e.what() << std::endl;
    }
    catch (...) {
        std::cerr << "Caught unknown exception." << std::endl;
    }
    cout << endl << "ALL DONE." << endl << endl;

    return 0;
}