diff --git a/DIRECTORY.md b/DIRECTORY.md index d0f9f70c370..a11d2108775 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -2,14 +2,14 @@ ## Src * Backtracking - * [All Combination Of Size K](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/all_combination_of_size_k.rs) + * [All Combinations of Size K](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/all_combination_of_size_k.rs) * [Graph Coloring](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/graph_coloring.rs) * [Hamiltonian Cycle](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/hamiltonian_cycle.rs) * [Knight Tour](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/knight_tour.rs) * [N-Queens](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/n_queens.rs) * [Parentheses Generator](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/parentheses_generator.rs) * [Permutations](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/permutations.rs) - * [Rat In Maze](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/rat_in_maze.rs) + * [Rat in Maze](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/rat_in_maze.rs) * [Subset Sum](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/subset_sum.rs) * [Sudoku](https://github.com/TheAlgorithms/Rust/blob/master/src/backtracking/sudoku.rs) * Big Integer @@ -17,11 +17,12 @@ * [Multiply](https://github.com/TheAlgorithms/Rust/blob/master/src/big_integer/multiply.rs) * [Poly1305](https://github.com/TheAlgorithms/Rust/blob/master/src/big_integer/poly1305.rs) * Bit Manipulation + * [Binary Coded Decimal](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/binary_coded_decimal.rs) * [Counting Bits](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/counting_bits.rs) * [Highest Set Bit](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/highest_set_bit.rs) * [N Bits Gray Code](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/n_bits_gray_code.rs) * [Reverse Bits](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/reverse_bits.rs) - * [Sum Of Two Integers](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/sum_of_two_integers.rs) + * [Sum of Two Integers](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/sum_of_two_integers.rs) * [Swap Odd and Even Bits](https://github.com/TheAlgorithms/Rust/blob/master/src/bit_manipulation/swap_odd_even_bits.rs) * Ciphers * [AES](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/aes.rs) @@ -33,11 +34,12 @@ * [Chacha](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/chacha.rs) * [Diffie-Hellman](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/diffie_hellman.rs) * [Hashing Traits](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/hashing_traits.rs) - * [Kerninghan](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/kerninghan.rs) + * [Kernighan](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/kernighan.rs) * [Morse Code](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/morse_code.rs) * [Polybius](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/polybius.rs) * [Rail Fence](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/rail_fence.rs) * [ROT13](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/rot13.rs) + * [RSA Cipher](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/rsa_cipher.rs) * [Salsa](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/salsa.rs) * [SHA-256](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/sha256.rs) * [SHA-3](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/sha3.rs) @@ -47,22 +49,22 @@ * [Vigenere](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/vigenere.rs) * [XOR](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/xor.rs) * Compression - * [Move To Front](https://github.com/TheAlgorithms/Rust/blob/master/src/compression/move_to_front.rs) + * [Move to Front](https://github.com/TheAlgorithms/Rust/blob/master/src/compression/move_to_front.rs) * [Run Length Encoding](https://github.com/TheAlgorithms/Rust/blob/master/src/compression/run_length_encoding.rs) * Conversions - * [Binary To Decimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/binary_to_decimal.rs) - * [Binary To Hexadecimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/binary_to_hexadecimal.rs) - * [Binary To Octal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/binary_to_octal.rs) - * [Decimal To Binary](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/decimal_to_binary.rs) - * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/decimal_to_hexadecimal.rs) - * [Decimal To Octal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/decimal_to_octal.rs) - * [Hexadecimal To Binary](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/hexadecimal_to_binary.rs) - * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/hexadecimal_to_decimal.rs) - * [Hexadecimal To Octal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/hexadecimal_to_octal.rs) + * [Binary to Decimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/binary_to_decimal.rs) + * [Binary to Hexadecimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/binary_to_hexadecimal.rs) + * [Binary to Octal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/binary_to_octal.rs) + * [Decimal to Binary](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/decimal_to_binary.rs) + * [Decimal to Hexadecimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/decimal_to_hexadecimal.rs) + * [Decimal to Octal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/decimal_to_octal.rs) + * [Hexadecimal to Binary](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/hexadecimal_to_binary.rs) + * [Hexadecimal to Decimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/hexadecimal_to_decimal.rs) + * [Hexadecimal to Octal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/hexadecimal_to_octal.rs) * [Length Conversion](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/length_conversion.rs) - * [Octal To Binary](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/octal_to_binary.rs) - * [Octal To Decimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/octal_to_decimal.rs) - * [Octal To Hexadecimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/octal_to_hexadecimal.rs) + * [Octal to Binary](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/octal_to_binary.rs) + * [Octal to Decimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/octal_to_decimal.rs) + * [Octal to Hexadecimal](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/octal_to_hexadecimal.rs) * [RGB-CMYK Conversion](https://github.com/TheAlgorithms/Rust/blob/master/src/conversions/rgb_cmyk_conversion.rs) * Data Structures * [AVL Tree](https://github.com/TheAlgorithms/Rust/blob/master/src/data_structures/avl_tree.rs) @@ -104,7 +106,7 @@ * [Maximal Square](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/maximal_square.rs) * [Maximum Subarray](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/maximum_subarray.rs) * [Minimum Cost Path](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/minimum_cost_path.rs) - * [Optimal Bst](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/optimal_bst.rs) + * [Optimal BST](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/optimal_bst.rs) * [Rod Cutting](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/rod_cutting.rs) * [Smith-Waterman](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/smith_waterman.rs) * [Snail](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/snail.rs) @@ -151,7 +153,7 @@ * [Centroid Decomposition](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/centroid_decomposition.rs) * [Decremental Connectivity](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/decremental_connectivity.rs) * [Depth First Search](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/depth_first_search.rs) - * [Depth First Search Tic Tac Toe](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/depth_first_search_tic_tac_toe.rs) + * [Depth First Search Tic-Tac-Toe](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/depth_first_search_tic_tac_toe.rs) * [Detect Cycle](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/detect_cycle.rs) * [Dijkstra](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/dijkstra.rs) * [Dinic Maxflow](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/dinic_maxflow.rs) diff --git a/src/bit_manipulation/binary_coded_decimal.rs b/src/bit_manipulation/binary_coded_decimal.rs new file mode 100644 index 00000000000..91f1076c3e5 --- /dev/null +++ b/src/bit_manipulation/binary_coded_decimal.rs @@ -0,0 +1,129 @@ +//! Binary Coded Decimal (BCD) conversion +//! +//! This module provides a function to convert decimal integers to Binary Coded Decimal (BCD) format. +//! In BCD, each decimal digit is represented by its 4-bit binary equivalent. +//! +//! # Examples +//! +//! ``` +//! use the_algorithms_rust::bit_manipulation::binary_coded_decimal; +//! +//! assert_eq!(binary_coded_decimal(12), "0b00010010"); +//! assert_eq!(binary_coded_decimal(987), "0b100110000111"); +//! ``` + +use std::fmt::Write; + +/// Converts a decimal integer to Binary Coded Decimal (BCD) format. +/// +/// Each digit of the input number is represented by a 4-bit binary value. +/// Negative numbers are treated as 0. +/// +/// # Arguments +/// +/// * `number` - An integer to be converted to BCD format +/// +/// # Returns +/// +/// A `String` representing the BCD encoding with "0b" prefix +/// +/// # Examples +/// +/// ``` +/// use the_algorithms_rust::bit_manipulation::binary_coded_decimal; +/// +/// assert_eq!(binary_coded_decimal(0), "0b0000"); +/// assert_eq!(binary_coded_decimal(3), "0b0011"); +/// assert_eq!(binary_coded_decimal(12), "0b00010010"); +/// assert_eq!(binary_coded_decimal(987), "0b100110000111"); +/// assert_eq!(binary_coded_decimal(-5), "0b0000"); +/// ``` +/// +/// # Algorithm +/// +/// 1. Convert the number to its absolute value (negative numbers become 0) +/// 2. For each decimal digit: +/// - Convert the digit to binary +/// - Pad to 4 bits with leading zeros +/// - Concatenate to the result +/// 3. Prepend "0b" to the final binary string +pub fn binary_coded_decimal(number: i32) -> String { + // Handle negative numbers by converting to 0 + let num = if number < 0 { 0 } else { number }; + + // Convert to string to process each digit + let digits = num.to_string(); + + // Build the BCD string using fold for efficiency + let bcd = digits.chars().fold(String::new(), |mut acc, digit| { + // Convert char to digit value and format as 4-bit binary + let digit_value = digit.to_digit(10).unwrap(); + write!(acc, "{digit_value:04b}").unwrap(); + acc + }); + + format!("0b{bcd}") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_zero() { + assert_eq!(binary_coded_decimal(0), "0b0000"); + } + + #[test] + fn test_single_digit() { + assert_eq!(binary_coded_decimal(1), "0b0001"); + assert_eq!(binary_coded_decimal(2), "0b0010"); + assert_eq!(binary_coded_decimal(3), "0b0011"); + assert_eq!(binary_coded_decimal(4), "0b0100"); + assert_eq!(binary_coded_decimal(5), "0b0101"); + assert_eq!(binary_coded_decimal(6), "0b0110"); + assert_eq!(binary_coded_decimal(7), "0b0111"); + assert_eq!(binary_coded_decimal(8), "0b1000"); + assert_eq!(binary_coded_decimal(9), "0b1001"); + } + + #[test] + fn test_two_digits() { + assert_eq!(binary_coded_decimal(10), "0b00010000"); + assert_eq!(binary_coded_decimal(12), "0b00010010"); + assert_eq!(binary_coded_decimal(25), "0b00100101"); + assert_eq!(binary_coded_decimal(99), "0b10011001"); + } + + #[test] + fn test_three_digits() { + assert_eq!(binary_coded_decimal(100), "0b000100000000"); + assert_eq!(binary_coded_decimal(123), "0b000100100011"); + assert_eq!(binary_coded_decimal(456), "0b010001010110"); + assert_eq!(binary_coded_decimal(987), "0b100110000111"); + } + + #[test] + fn test_large_numbers() { + assert_eq!(binary_coded_decimal(1234), "0b0001001000110100"); + assert_eq!(binary_coded_decimal(9999), "0b1001100110011001"); + } + + #[test] + fn test_negative_numbers() { + // Negative numbers should be treated as 0 + assert_eq!(binary_coded_decimal(-1), "0b0000"); + assert_eq!(binary_coded_decimal(-2), "0b0000"); + assert_eq!(binary_coded_decimal(-100), "0b0000"); + } + + #[test] + fn test_each_digit_encoding() { + // Verify that each digit is encoded correctly in a multi-digit number + // 67 should be: 6 (0110) and 7 (0111) + assert_eq!(binary_coded_decimal(67), "0b01100111"); + + // 305 should be: 3 (0011), 0 (0000), 5 (0101) + assert_eq!(binary_coded_decimal(305), "0b001100000101"); + } +} diff --git a/src/bit_manipulation/mod.rs b/src/bit_manipulation/mod.rs index c68e4191af0..f00bee29bdd 100644 --- a/src/bit_manipulation/mod.rs +++ b/src/bit_manipulation/mod.rs @@ -1,3 +1,4 @@ +mod binary_coded_decimal; mod counting_bits; mod highest_set_bit; mod n_bits_gray_code; @@ -5,6 +6,7 @@ mod reverse_bits; mod sum_of_two_integers; mod swap_odd_even_bits; +pub use binary_coded_decimal::binary_coded_decimal; pub use counting_bits::count_set_bits; pub use highest_set_bit::find_highest_set_bit; pub use n_bits_gray_code::generate_gray_code; diff --git a/src/ciphers/base64.rs b/src/ciphers/base64.rs index 81d4ac5dd6a..90aca832cfe 100644 --- a/src/ciphers/base64.rs +++ b/src/ciphers/base64.rs @@ -69,7 +69,7 @@ pub fn base64_decode(data: &str) -> Result, (&str, u8)> { 'decodeloop: loop { while collected_bits < 8 { if let Some(nextbyte) = databytes.next() { - // Finds the first occurence of the latest byte + // Finds the first occurrence of the latest byte if let Some(idx) = CHARSET.iter().position(|&x| x == nextbyte) { byte_buffer |= ((idx & 0b00111111) as u16) << (10 - collected_bits); collected_bits += 6; diff --git a/src/ciphers/chacha.rs b/src/ciphers/chacha.rs index 6b0440a9d11..c6f0735ccea 100644 --- a/src/ciphers/chacha.rs +++ b/src/ciphers/chacha.rs @@ -29,7 +29,7 @@ pub const C: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; /// data. /// /// The 16 input numbers can be thought of as the elements of a 4x4 matrix like -/// the one bellow, on which we do the main operations of the cipher. +/// the one below, on which we do the main operations of the cipher. /// /// ```text /// +----+----+----+----+ @@ -43,7 +43,7 @@ pub const C: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; /// +----+----+----+----+ /// ``` /// -/// As per the diagram bellow, `input[0, 1, 2, 3]` are the constants mentioned +/// As per the diagram below, `input[0, 1, 2, 3]` are the constants mentioned /// above, `input[4..=11]` is filled with the key, and `input[6..=9]` should be /// filled with nonce and counter values. The output of the function is stored /// in `output` variable and can be XORed with the plain text to produce the diff --git a/src/ciphers/kernighan.rs b/src/ciphers/kernighan.rs new file mode 100644 index 00000000000..c8144990fa2 --- /dev/null +++ b/src/ciphers/kernighan.rs @@ -0,0 +1,23 @@ +pub fn kernighan(n: u32) -> i32 { + let mut count = 0; + let mut n = n; + + while n > 0 { + n = n & (n - 1); + count += 1; + } + + count +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn count_set_bits() { + assert_eq!(kernighan(0b0000_0000_0000_0000_0000_0000_0000_1011), 3); + assert_eq!(kernighan(0b0000_0000_0000_0000_0000_0000_1000_0000), 1); + assert_eq!(kernighan(0b1111_1111_1111_1111_1111_1111_1111_1101), 31); + } +} diff --git a/src/ciphers/kerninghan.rs b/src/ciphers/kerninghan.rs deleted file mode 100644 index 4263850ff3f..00000000000 --- a/src/ciphers/kerninghan.rs +++ /dev/null @@ -1,23 +0,0 @@ -pub fn kerninghan(n: u32) -> i32 { - let mut count = 0; - let mut n = n; - - while n > 0 { - n = n & (n - 1); - count += 1; - } - - count -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn count_set_bits() { - assert_eq!(kerninghan(0b0000_0000_0000_0000_0000_0000_0000_1011), 3); - assert_eq!(kerninghan(0b0000_0000_0000_0000_0000_0000_1000_0000), 1); - assert_eq!(kerninghan(0b1111_1111_1111_1111_1111_1111_1111_1101), 31); - } -} diff --git a/src/ciphers/mod.rs b/src/ciphers/mod.rs index f7a55b0014d..277fa7c4a5a 100644 --- a/src/ciphers/mod.rs +++ b/src/ciphers/mod.rs @@ -7,11 +7,12 @@ mod caesar; mod chacha; mod diffie_hellman; mod hashing_traits; -mod kerninghan; +mod kernighan; mod morse_code; mod polybius; mod rail_fence; mod rot13; +mod rsa_cipher; mod salsa; mod sha256; mod sha3; @@ -20,6 +21,7 @@ mod theoretical_rot13; mod transposition; mod vigenere; mod xor; + pub use self::aes::{aes_decrypt, aes_encrypt, AesKey}; pub use self::another_rot13::another_rot13; pub use self::baconian_cipher::{baconian_decode, baconian_encode}; @@ -30,11 +32,14 @@ pub use self::chacha::chacha20; pub use self::diffie_hellman::DiffieHellman; pub use self::hashing_traits::Hasher; pub use self::hashing_traits::HMAC; -pub use self::kerninghan::kerninghan; +pub use self::kernighan::kernighan; pub use self::morse_code::{decode, encode}; pub use self::polybius::{decode_ascii, encode_ascii}; pub use self::rail_fence::{rail_fence_decrypt, rail_fence_encrypt}; pub use self::rot13::rot13; +pub use self::rsa_cipher::{ + decrypt, decrypt_text, encrypt, encrypt_text, generate_keypair, PrivateKey, PublicKey, +}; pub use self::salsa::salsa20; pub use self::sha256::SHA256; pub use self::sha3::{sha3_224, sha3_256, sha3_384, sha3_512}; diff --git a/src/ciphers/rsa_cipher.rs b/src/ciphers/rsa_cipher.rs new file mode 100644 index 00000000000..f35d87ffd17 --- /dev/null +++ b/src/ciphers/rsa_cipher.rs @@ -0,0 +1,405 @@ +//! RSA Cipher Implementation +//! +//! This module provides a basic implementation of the RSA (Rivest-Shamir-Adleman) encryption algorithm. +//! RSA is an asymmetric cryptographic algorithm that uses a pair of keys: public and private. +//! +//! # Warning +//! +//! This is an educational implementation and should NOT be used for production cryptography. +//! Use established cryptographic libraries like `ring` or `rust-crypto` for real-world applications. +//! +//! # Examples +//! +//! ``` +//! use the_algorithms_rust::ciphers::{generate_keypair, encrypt, decrypt}; +//! +//! let (public_key, private_key) = generate_keypair(61, 53); +//! let message = 65; +//! let encrypted = encrypt(message, &public_key); +//! let decrypted = decrypt(encrypted, &private_key); +//! assert_eq!(message, decrypted); +//! ``` + +/// Represents an RSA public key containing (n, e) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PublicKey { + pub n: u64, + pub e: u64, +} + +/// Represents an RSA private key containing (n, d) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PrivateKey { + pub n: u64, + pub d: u64, +} + +/// Computes the greatest common divisor using Euclid's algorithm +/// +/// # Arguments +/// +/// * `a` - First number +/// * `b` - Second number +/// +/// # Returns +/// +/// The GCD of `a` and `b` +fn gcd(mut a: u64, mut b: u64) -> u64 { + while b != 0 { + let temp = b; + b = a % b; + a = temp; + } + a +} + +/// Computes the modular multiplicative inverse using the Extended Euclidean Algorithm +/// +/// Finds `x` such that `(a * x) % m == 1` +/// +/// # Arguments +/// +/// * `a` - The number to find the inverse of +/// * `m` - The modulus +/// +/// # Returns +/// +/// The modular multiplicative inverse of `a` modulo `m`, or `None` if it doesn't exist +fn mod_inverse(a: i64, m: i64) -> Option { + let (mut old_r, mut r) = (a, m); + let (mut old_s, mut s) = (1_i64, 0_i64); + + while r != 0 { + let quotient = old_r / r; + (old_r, r) = (r, old_r - quotient * r); + (old_s, s) = (s, old_s - quotient * s); + } + + if old_r > 1 { + return None; // a is not invertible + } + + if old_s < 0 { + Some((old_s + m) as u64) + } else { + Some(old_s as u64) + } +} + +/// Performs modular exponentiation: (base^exp) % modulus +/// +/// Uses the square-and-multiply algorithm for efficiency +/// +/// # Arguments +/// +/// * `base` - The base number +/// * `exp` - The exponent +/// * `modulus` - The modulus +/// +/// # Returns +/// +/// The result of (base^exp) % modulus +fn mod_pow(mut base: u64, mut exp: u64, modulus: u64) -> u64 { + if modulus == 1 { + return 0; + } + + let mut result = 1; + base %= modulus; + + while exp > 0 { + if exp % 2 == 1 { + result = ((result as u128 * base as u128) % modulus as u128) as u64; + } + exp >>= 1; + base = ((base as u128 * base as u128) % modulus as u128) as u64; + } + + result +} + +/// Generates an RSA keypair from two prime numbers +/// +/// # Arguments +/// +/// * `p` - First prime number +/// * `q` - Second prime number (should be different from p) +/// +/// # Returns +/// +/// A tuple containing (PublicKey, PrivateKey) +/// +/// # Examples +/// +/// ``` +/// use the_algorithms_rust::ciphers::generate_keypair; +/// +/// let (public, private) = generate_keypair(61, 53); +/// // n = p * q +/// assert_eq!(public.n, 3233); +/// assert_eq!(private.n, 3233); +/// // Both keys share the same n +/// assert_eq!(public.n, private.n); +/// ``` +/// +/// # Panics +/// +/// Panics if the modular inverse cannot be computed +pub fn generate_keypair(p: u64, q: u64) -> (PublicKey, PrivateKey) { + let n = p * q; + let phi = (p - 1) * (q - 1); + + // Choose e such that 1 < e < phi and gcd(e, phi) = 1 + let mut e = 2; + while e < phi { + if gcd(e, phi) == 1 { + break; + } + e += 1; + } + + // Compute d, the modular multiplicative inverse of e mod phi + let d = mod_inverse(e as i64, phi as i64).expect("Failed to compute modular inverse"); + + let public_key = PublicKey { n, e }; + let private_key = PrivateKey { n, d }; + + (public_key, private_key) +} + +/// Encrypts a message using the RSA public key +/// +/// # Arguments +/// +/// * `message` - The plaintext message (must be less than n) +/// * `public_key` - The public key to use for encryption +/// +/// # Returns +/// +/// The encrypted ciphertext +/// +/// # Examples +/// +/// ``` +/// use the_algorithms_rust::ciphers::{generate_keypair, encrypt, decrypt}; +/// +/// let (public_key, private_key) = generate_keypair(61, 53); +/// let message = 65; +/// let ciphertext = encrypt(message, &public_key); +/// let decrypted = decrypt(ciphertext, &private_key); +/// assert_eq!(decrypted, message); +/// ``` +pub fn encrypt(message: u64, public_key: &PublicKey) -> u64 { + mod_pow(message, public_key.e, public_key.n) +} + +/// Decrypts a ciphertext using the RSA private key +/// +/// # Arguments +/// +/// * `ciphertext` - The encrypted message +/// * `private_key` - The private key to use for decryption +/// +/// # Returns +/// +/// The decrypted plaintext message +/// +/// # Examples +/// +/// ``` +/// use the_algorithms_rust::ciphers::{generate_keypair, encrypt, decrypt}; +/// +/// let (public_key, private_key) = generate_keypair(61, 53); +/// let message = 65; +/// let ciphertext = encrypt(message, &public_key); +/// let plaintext = decrypt(ciphertext, &private_key); +/// assert_eq!(plaintext, message); +/// ``` +pub fn decrypt(ciphertext: u64, private_key: &PrivateKey) -> u64 { + mod_pow(ciphertext, private_key.d, private_key.n) +} + +/// Encrypts a text message by converting each character to its ASCII value +/// +/// # Arguments +/// +/// * `message` - The plaintext string +/// * `public_key` - The public key to use for encryption +/// +/// # Returns +/// +/// A vector of encrypted values, one for each character +/// +/// # Examples +/// +/// ``` +/// use the_algorithms_rust::ciphers::{generate_keypair, encrypt_text, decrypt_text}; +/// +/// let (public, private) = generate_keypair(61, 53); +/// let encrypted = encrypt_text("HI", &public); +/// let decrypted = decrypt_text(&encrypted, &private); +/// assert_eq!(decrypted, "HI"); +/// ``` +pub fn encrypt_text(message: &str, public_key: &PublicKey) -> Vec { + message + .chars() + .map(|c| encrypt(c as u64, public_key)) + .collect() +} + +/// Decrypts a vector of encrypted values back to text +/// +/// # Arguments +/// +/// * `ciphertext` - The vector of encrypted character values +/// * `private_key` - The private key to use for decryption +/// +/// # Returns +/// +/// The decrypted string +/// +/// # Examples +/// +/// ``` +/// use the_algorithms_rust::ciphers::{generate_keypair, encrypt_text, decrypt_text}; +/// +/// let (public, private) = generate_keypair(61, 53); +/// let encrypted = encrypt_text("HELLO", &public); +/// let decrypted = decrypt_text(&encrypted, &private); +/// assert_eq!(decrypted, "HELLO"); +/// ``` +pub fn decrypt_text(ciphertext: &[u64], private_key: &PrivateKey) -> String { + ciphertext + .iter() + .map(|&c| { + let decrypted = decrypt(c, private_key); + char::from_u32(decrypted as u32).unwrap_or('?') + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_gcd() { + assert_eq!(gcd(48, 18), 6); + assert_eq!(gcd(17, 13), 1); + assert_eq!(gcd(100, 50), 50); + assert_eq!(gcd(7, 7), 7); + } + + #[test] + fn test_mod_inverse() { + assert_eq!(mod_inverse(17, 3120), Some(2753)); + assert_eq!(mod_inverse(7, 40), Some(23)); + assert!(mod_inverse(4, 8).is_none()); // No inverse exists + } + + #[test] + fn test_mod_pow() { + assert_eq!(mod_pow(4, 13, 497), 445); + assert_eq!(mod_pow(2, 10, 1000), 24); + assert_eq!(mod_pow(3, 5, 7), 5); + } + + #[test] + fn test_generate_keypair() { + let (public, private) = generate_keypair(61, 53); + + // n should be p * q + assert_eq!(public.n, 3233); + assert_eq!(private.n, 3233); + + // e should be coprime with phi + let phi = (61 - 1) * (53 - 1); + assert_eq!(gcd(public.e, phi), 1); + + // Verify that (e * d) % phi == 1 + assert_eq!((public.e * private.d) % phi, 1); + } + + #[test] + fn test_encrypt_decrypt_number() { + let (public, private) = generate_keypair(61, 53); + let message = 65; + + let encrypted = encrypt(message, &public); + // Encrypted value will vary based on e, so we just check it's different + assert_ne!(encrypted, message); + + let decrypted = decrypt(encrypted, &private); + assert_eq!(decrypted, message); + } + + #[test] + fn test_encrypt_decrypt_various_numbers() { + let (public, private) = generate_keypair(61, 53); + + for message in [1, 42, 100, 255, 1000, 3000] { + let encrypted = encrypt(message, &public); + let decrypted = decrypt(encrypted, &private); + assert_eq!(decrypted, message, "Failed for message: {message}"); + } + } + + #[test] + fn test_encrypt_decrypt_text() { + let (public, private) = generate_keypair(61, 53); + + let message = "HI"; + let encrypted = encrypt_text(message, &public); + let decrypted = decrypt_text(&encrypted, &private); + + assert_eq!(decrypted, message); + } + + #[test] + fn test_encrypt_decrypt_longer_text() { + let (public, private) = generate_keypair(61, 53); + + let message = "HELLO"; + let encrypted = encrypt_text(message, &public); + let decrypted = decrypt_text(&encrypted, &private); + + assert_eq!(decrypted, message); + } + + #[test] + fn test_different_primes() { + let (public, private) = generate_keypair(17, 19); + + let message = 42; + let encrypted = encrypt(message, &public); + let decrypted = decrypt(encrypted, &private); + + assert_eq!(decrypted, message); + } + + #[test] + fn test_encrypt_decrypt_alphabet() { + let (public, private) = generate_keypair(61, 53); + + let message = "ABC"; + let encrypted = encrypt_text(message, &public); + let decrypted = decrypt_text(&encrypted, &private); + + assert_eq!(decrypted, message); + } + + #[test] + fn test_key_properties() { + let (public, private) = generate_keypair(61, 53); + + // Both keys should have the same n + assert_eq!(public.n, private.n); + + // e and d should be different + assert_ne!(public.e, private.d); + + // Verify that (e * d) % phi == 1 + let phi = (61 - 1) * (53 - 1); + assert_eq!((public.e * private.d) % phi, 1); + } +} diff --git a/src/ciphers/salsa.rs b/src/ciphers/salsa.rs index 83b37556ff1..e6adba648f4 100644 --- a/src/ciphers/salsa.rs +++ b/src/ciphers/salsa.rs @@ -19,7 +19,7 @@ macro_rules! quarter_round { /// seems to be a sane choice. /// /// The 16 input numbers can be thought of as the elements of a 4x4 matrix like -/// the one bellow, on which we do the main operations of the cipher. +/// the one below, on which we do the main operations of the cipher. /// /// ```text /// +----+----+----+----+ @@ -33,7 +33,7 @@ macro_rules! quarter_round { /// +----+----+----+----+ /// ``` /// -/// As per the diagram bellow, `input[0, 5, 10, 15]` are the constants mentioned +/// As per the diagram below, `input[0, 5, 10, 15]` are the constants mentioned /// above, `input[1, 2, 3, 4, 11, 12, 13, 14]` is filled with the key, and /// `input[6, 7, 8, 9]` should be filled with nonce and counter values. The output /// of the function is stored in `output` variable and can be XORed with the diff --git a/src/string/isogram.rs b/src/string/isogram.rs index 30b8d66bdff..f4fc8cfd981 100644 --- a/src/string/isogram.rs +++ b/src/string/isogram.rs @@ -56,7 +56,7 @@ fn count_letters(s: &str) -> Result, IsogramError> { /// # Return /// /// - `Ok(true)` if all characters appear only once, or `Ok(false)` if any character appears more than once. -/// - `Err(IsogramError::NonAlphabeticCharacter) if the input contains any non-alphabetic characters. +/// - `Err(IsogramError::NonAlphabeticCharacter)` if the input contains any non-alphabetic characters. pub fn is_isogram(s: &str) -> Result { let letter_counts = count_letters(s)?; Ok(letter_counts.values().all(|&count| count == 1)) @@ -89,7 +89,7 @@ mod tests { isogram_sentences: ("The big dwarf only jumps", Ok(true)), isogram_french: ("Lampez un fort whisky", Ok(true)), isogram_portuguese: ("Velho traduz sim", Ok(true)), - isogram_spanis: ("Centrifugadlos", Ok(true)), + isogram_spanish: ("Centrifugadlos", Ok(true)), invalid_isogram_with_repeated_char: ("hello", Ok(false)), invalid_isogram_with_numbers: ("abc123", Err(IsogramError::NonAlphabeticCharacter)), invalid_isogram_with_special_char: ("abc!", Err(IsogramError::NonAlphabeticCharacter)),