Big integer algorithm [02] basic operations (Maintenance Algorithm), integer 02
The previous blog briefly introduced the big integer representation method. This time, we will introduce some basic algorithms.
★Initialization and cleanup
The starting point of writing a big integer function is the initialization and clearing of the bignum structure. These two algorithms are used in most other algorithms.
For the given bignum structure, there are two initialization cases: one is to point the dp of the bignum structure to NULL, and the other is to allocate a certain amount of dynamic memory during initialization, and point the dp pointer to the memory. In fact, I originally planned to use only the second method for initialization, but considering that the initial memory may be allocated too much, resulting in memory waste, I decided to use the two methods together. The advantage of the first method is that in subsequent programming, you don't have to consider how much memory should be allocated in advance. The memory allocation work should be done by the bn_grow function, which reduces the workload, however, its defects are also obvious. If the initial memory allocation is too small, the subsequent memory allocation may require multiple times to increase the accuracy and reduce the efficiency, you need to know that the operations on the stack are still quite time-consuming.
If the dp pointer in the bignum structure has been initialized (NULL or pointed to a allocated memory), other members of the structure must be initialized. Bignum is 0 by default after initialization, so sign = 1, used = 0. If dp is NULL, alloc = 0; otherwise, alloc = allocated digits (nlimbs ).
To avoid unlimited memory allocation, an upper limit BN_MAX_LIMB is set for the number, which is defined in the header file:
# Define BN_MAX_LIMB 25600
That is to say, the maximum number of digits in the bignum structure cannot exceed 25600. In a 32-bit environment, the length of each digit is 32bit, so the maximum length of bignum is 819200bit, which is more than enough for the encryption algorithm.
For ease of transplantation, the following macros are defined (for better understanding, refer to the definition of PolarSSL ):
# Define cel (sizeof (bn_digit) // chars in limb (the size of each digit in bytes)
# Define biL (CEL <3) // bits in limb (bit size of each digit)
# Define biLH (pencil <2) // half bits in limb (half the bit size of each digit, which will be used in subsequent modulo operations)
# Define BN_MALLOC malloc
# Define BN_FREE free
/*** Initialize the bignum structure without allocating memory. */Void bn_init (bignum * x) {if (x! = NULL) {x-> sign = 1; x-> alloc = 0; x-> used = 0; x-> dp = NULL ;}} /*** initialize the bignum structure and allocate memory. * If initialization fails (Memory Allocation Error), BN_MEMORY_ALLOCATE_FAIL is returned. * If the number exceeds the upper limit, BN_EXCEED_MAX_LIMB */int bn_init_size (bignum * x, size_t nlimbs) {bn_dig; if (nlimbs> BN_MAX_LIMB) return BN_EXCEED_MAX_LIMB; if (x! = NULL) {p = (bn_digit *) BN_MALLOC (nlimbs * CEL); if (p = NULL) return BN_MEMORY_ALLOCATE_FAIL; memset (p, 0x00, nlimbs * CEL ); // After initialization, the default value of bignum is 0. Therefore, you must set all values of memory pointed to by p to 0. x-> sign = 1; x-> alloc = nlimbs; x-> used = 0; x-> dp = p;} return 0 ;}
When you no longer need to use bignum, you should use the bn_free function to release the allocated memory in time. The bn_free function has two functions: one is to clear the digits of bignum and other Members, so that even if you accidentally use the bignum that has been cleared, there will be no problem, and the other is to release the allocated memory.
Set the dp of the bignum that has been cleared to NULL, so that when you call this function again, the function can detect that the bignum has been cleared to Avoid Multiple Memory releases.
void bn_free(bignum *x){ if(x != NULL) { if(x->dp != NULL) { memset(x->dp, 0x00, x->alloc * ciL); BN_FREE(x->dp); } x->sign = 1; x->alloc = 0; x->used = 0; x->dp = NULL; }}
★Precision increase
When you store a value in bignum, you must have enough digits to store a complete big integer without loss. If alloc is large enough, you only need to add used. Otherwise, you need to re-allocate the memory and increase alloc to meet the requirements.
The bn_grow function first checks whether the required number (nlimbs) is greater than alloc. If not, no precision is required and the function call ends. This avoids memory reallocation and saves time. If nlimbs is greater than alloc, it is necessary to re-allocate a memory space with a length of nlimbs. After initializing to 0, copy the content in the memory that the original dp points to the new space, and release the memory that the original dp points.
Int bn_grow (bignum * x, size_t nlimbs) {bn_digit * p; if (nlimbs> BN_MAX_LIMB) return callback; if (nlimbs> x-> alloc) {p = (bn_digit *) BN_MALLOC (nlimbs * CER); if (p = NULL) return BN_MEMORY_ALLOCATE_FAIL; memset (p, 0, nlimbs * CER); if (x-> dp! = NULL) // check whether the memory is allocated before the bignum structure's dp. If there is a allocation, perform the replication and memory release operations. {Memcpy (p, x-> dp, x-> alloc * CER); memset (x-> dp, 0, x-> alloc * CER ); BN_FREE (x-> dp);} x-> dp = p; x-> alloc = nlimbs;} return 0 ;}
★Copy operation
This is simple. Copy bignum y to bignum x. Note that x-> dp points to the new memory instead of y-> dp.
Int bn_copy (bignum * x, const bignum * y) {int ret; size_t nlimbs; if (x = y) return 0; // x = y, it indicates that x and y are the same number. In this case, no operation is required. Note that both x and y are pointers x-> sign = y-> sign; x-> used = y-> used; nlimbs = (y-> used = 0 )? 1: y-> used; // if y is 0, BN_CHECK (bn_grow (x, nlimbs) is allocated to x by default. memset (x-> dp, 0x00, x-> alloc * CEL); // initialize the memory if (y-> dp! = NULL & y-> used> 0) memcpy (x-> dp, y-> dp, y-> used * CEL); clean: return ret ;}
★Single-precision assignment
Sometimes we want to set bignum to a relatively small number of Single-Precision values, such as 1 and 2 ^ 32-1. Assign a single precision value to the first unit of the array by allocating one digit of memory. Here, the default Single-precision number is unsigned (that is, a non-negative integer). To assign a negative value, set sign to-1.
Int bn_set_word (bignum * x, const bn_digit word) {int ret; BN_CHECK (bn_grow (x, 1); // allocate a memory for memset (x-> dp, 0, x-> alloc * CEL); // initialize x-> dp [0] = word; x-> used = (word! = 0 )? 1: 0; x-> sign = 1; // if it is a negative number, set sign to-1 after the function is called to clean: return ret ;}
★Exchange Operation
This function is very simple, that is, to exchange two big integers x and y. The specific implementation principle is also very simple, just exchange each member in the struct.
void bn_swap(bignum *x, bignum *y){ int tmp_sign; size_t tmp_alloc; size_t tmp_used; bn_digit *tmp_dp; tmp_sign = x->sign; tmp_alloc = x->alloc; tmp_used = x->used; tmp_dp = x->dp; y->sign = x->sign; y->alloc = x->alloc; y->used = x->used; y->dp = x->dp; x->sign = tmp_sign; x->alloc = tmp_alloc; x->used = tmp_used; x->dp = tmp_dp;}
★Summary
These algorithms described in this article are the most basic algorithms in the big integer library. Although they are very simple, they are the cornerstone of other algorithms and will be frequently used by subsequent algorithms. The next article will talk about how to compare two big integers, including the absolute value comparison, the signed big integer comparison, and the comparison between the big integer and the single precision number.