Skip to content

Commit b0b1050

Browse files
committed
Quartz sync: Sep 7, 2024, 8:51 PM
1 parent 8393e9c commit b0b1050

File tree

4 files changed

+169
-174
lines changed

4 files changed

+169
-174
lines changed

content/Miller-Rabin primality test.md

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,48 +24,48 @@ tags: []
2424

2525
## Rust Implementation
2626
```rust
27-
fn miller_rabin(n: u64) -> bool {
28-
let witnesses = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
29-
for &a in witnesses.iter() {
30-
if n == a {
31-
return true;
32-
}
33-
34-
if !witness(a, n) {
35-
return false;
36-
}
27+
fn miller_rabin_test(n: &BigUint, a: &BigUint) -> bool {
28+
if *n == BigUint::from(2u32) {
29+
return true;
3730
}
38-
true
39-
}
40-
```
41-
- This function tests the number against a fixed set of "witnesses".
42-
- If the number fails the test for any witness, it's composite.
43-
- If it passes for all witnesses, it's probably prime.
44-
45-
```rust
46-
fn witness(a: u64, n: u64) -> bool {
47-
let mut t = n - 1;
48-
let mut s = 0;
49-
50-
while t % 2 == 0 {
51-
t /= 2;
52-
s += 1;
31+
if n.is_even() {
32+
return false;
5333
}
5434

55-
let mut x = mod_pow(a, t, n);
56-
if x == 1 || x == n - 1 {
35+
let n_minus_one = n - 1u32;
36+
let s = n_minus_one.trailing_zeros().unwrap();
37+
let d = &n_minus_one >> s;
38+
39+
let mut x = a.modpow(&d, n);
40+
if x == BigUint::one() || x == n_minus_one {
5741
return true;
5842
}
5943

60-
for _ in 0..s-1 {
61-
x = mod_mul(x, x, n);
62-
if x == n - 1 {
44+
for _ in 0..s - 1 {
45+
x = (&x * &x) % n;
46+
if x == n_minus_one {
6347
return true;
6448
}
6549
}
50+
6651
false
6752
}
6853
```
69-
- This function performs the actual Miller-Rabin test for a single witness.
70-
- It first decomposes n-1 into (2^s) * t.
71-
- Then it computes a^t mod n and checks for the conditions that indicate primality.
54+
### Notes
55+
Here's a step-by-step explanation of the function:
56+
57+
1. The function takes two `BigUint` parameters: `n` (the number to test for primality) and `a` (a random base used in the test).
58+
2. First, it checks if `n` is 2, which is prime, so it returns `true`.
59+
3. If `n` is even (and not 2), it's not prime, so it returns `false`.
60+
4. It calculates `n_minus_one` as `n - 1`.
61+
5. It finds `s` and `d` such that `n - 1 = 2^s * d`, where `d` is odd:
62+
- `s` is the number of trailing zeros in the binary representation of `n_minus_one`.
63+
- `d` is `n_minus_one` right-shifted by `s` bits.
64+
6. It computes `x = a^d mod n` using the `modpow` function.
65+
7. If `x` is 1 or `n - 1`, it returns `true` (probably prime).
66+
8. It then enters a loop that runs `s - 1` times:
67+
- In each iteration, it squares `x` modulo `n`.
68+
- If `x` becomes `n - 1`, it returns `true` (probably prime).
69+
9. If the loop completes without returning, it returns `false` (composite).
70+
71+
This test is not definitive but provides a strong probabilistic indication of primality. To increase certainty, the test is typically run multiple times with different random bases `a`.

content/RSA.md

Lines changed: 108 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -15,167 +15,163 @@ tags: []
1515
6. Implement encryption: $c = m^e mod n$
1616
7. Implement decryption: $m = c^d mod n$
1717

18-
## Rust Implementation
18+
## Optimised Rust Implementation
1919
```rust
20-
use rand::Rng;
20+
use num_bigint::{BigInt, BigUint, RandBigInt, ToBigInt};
21+
use num_integer::Integer;
22+
use num_traits::{One, Zero};
23+
use rand::prelude::*;
24+
use rayon::prelude::*;
25+
use std::sync::Arc;
2126

22-
fn is_prime(n: u64) -> bool {
23-
if n <= 1 {
24-
return false;
27+
const SMALL_PRIMES: [u32; 12] = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
28+
29+
fn generate_prime(bits: u64) -> BigUint {
30+
let mut rng = thread_rng();
31+
loop {
32+
let n: BigUint = rng.gen_biguint(bits);
33+
if n.is_odd() && is_prime(&n) {
34+
return n;
35+
}
2536
}
37+
}
2638

27-
if n <= 3 {
39+
fn miller_rabin_test(n: &BigUint, a: &BigUint) -> bool {
40+
if *n == BigUint::from(2u32) {
2841
return true;
2942
}
30-
31-
if n % 2 == 0 || n % 3 == 0 {
43+
if n.is_even() {
3244
return false;
3345
}
3446

35-
let limit = (n as f64).sqrt() as u64;
36-
let mut i = 5;
37-
while i <= limit {
38-
if n % i == 0 || n % (i + 2) == 0 {
39-
return false;
40-
}
41-
i += 6;
42-
}
47+
let n_minus_one = n - 1u32;
48+
let s = n_minus_one.trailing_zeros().unwrap();
49+
let d = &n_minus_one >> s;
4350

44-
if n < 2_u64.pow(32) {
51+
let mut x = a.modpow(&d, n);
52+
if x == BigUint::one() || x == n_minus_one {
4553
return true;
4654
}
4755

48-
miller_rabin(n)
49-
}
50-
51-
fn miller_rabin(n: u64) -> bool {
52-
let witnesses = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
53-
for &a in witnesses.iter() {
54-
if n == a {
56+
for _ in 0..s - 1 {
57+
x = (&x * &x) % n;
58+
if x == n_minus_one {
5559
return true;
5660
}
57-
58-
if !witness(a, n) {
59-
return false;
60-
}
6161
}
62-
true
63-
}
6462

65-
fn witness(a: u64, n: u64) -> bool {
66-
let mut t = n - 1;
67-
let mut s = 0;
68-
69-
while t % 2 == 0 {
70-
t /= 2;
71-
s += 1;
72-
}
73-
74-
let mut x = mod_pow(a, t, n);
75-
if x == 1 || x == n - 1 {
76-
return true;
77-
}
63+
false
64+
}
7865

79-
for _ in 0..s - 1 {
80-
x = mod_mul(x, x, n);
81-
if x == n - 1 {
66+
fn is_prime(n: &BigUint) -> bool {
67+
for &p in &SMALL_PRIMES {
68+
if *n == p.into() {
8269
return true;
8370
}
71+
if n % p == BigUint::zero() {
72+
return false;
73+
}
8474
}
85-
false
86-
}
8775

88-
fn mod_pow(mut base: u64, mut exp: u64, modulus: u64) -> u64 {
89-
if modulus == 1 {
90-
return 0;
76+
if n < &BigUint::from(2047u32) {
77+
return miller_rabin_test(n, &BigUint::from(2u32));
9178
}
9279

93-
let mut result = 1;
94-
base %= modulus;
95-
while exp > 0 {
96-
if exp % 2 == 1 {
97-
result = mod_mul(result, base, modulus);
80+
let bases: Arc<Vec<BigUint>> = Arc::new(
81+
if n.bits() < 64 {
82+
vec![2u32, 3, 5, 7, 11, 13, 17]
83+
} else if n.bits() < 128 {
84+
vec![2u32, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
85+
} else {
86+
vec![2u32, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]
9887
}
88+
.into_iter()
89+
.map(BigUint::from)
90+
.collect::<Vec<BigUint>>(),
91+
);
9992

100-
exp >>= 1;
101-
base = mod_mul(base, base, modulus);
102-
}
103-
result
93+
bases.par_iter().all(|b| miller_rabin_test(n, b))
10494
}
10595

106-
fn mod_mul(a: u64, b: u64, m: u64) -> u64 {
107-
((a as u128 * b as u128) % m as u128) as u64
108-
}
96+
fn mod_inverse(a: &BigUint, m: &BigUint) -> Option<BigUint> {
97+
let a = a.to_bigint().unwrap();
98+
let m = m.to_bigint().unwrap();
99+
let (mut t, mut newt) = (BigInt::zero(), BigInt::one());
100+
let (mut r, mut newr) = (m.clone(), a);
109101

110-
fn generate_prime(min: u64, max: u64) -> u64 {
111-
let mut rng = rand::thread_rng();
112-
loop {
113-
let n = rng.gen_range(min..=max);
114-
if is_prime(n) {
115-
return n;
116-
}
102+
while !newr.is_zero() {
103+
let quotient = &r / &newr;
104+
(t, newt) = (newt.clone(), t - &quotient * &newt);
105+
(r, newr) = (newr.clone(), r - &quotient * &newr);
117106
}
118-
}
119107

120-
fn gcd(a: u64, b: u64) -> u64 {
121-
if b == 0 {
122-
a
108+
if r > BigInt::one() {
109+
None
123110
} else {
124-
gcd(b, a % b)
125-
}
126-
}
127-
128-
fn mod_inverse(a: u64, m: u64) -> Option<u64> {
129-
for i in 1..m {
130-
if (a * i) % m == 1 {
131-
return Some(i);
111+
while t < BigInt::zero() {
112+
t += &m;
132113
}
114+
Some((t % &m).to_biguint().unwrap())
133115
}
134-
Nne
135116
}
136117

137-
fn generate_keys() -> ((u64, u64), (u64, u64)) {
138-
let p = generate_prime(100, 1000);
139-
let q = generate_prime(100, 1000);
140-
let n = p * q;
141-
let phi = (p - 1) * (q - 1);
142-
143-
let mut e = 65537; // Common choice for e
144-
while gcd(e, phi) != 1 {
145-
e += 2;
146-
}
147-
148-
let d = mod_inverse(e, phi).unwrap();
149-
150-
((e, n), (d, n)) // ((public_key), (private_key))
118+
fn generate_keypair() -> (BigUint, BigUint, BigUint) {
119+
let (p, q) = rayon::join(|| generate_prime(1024), || generate_prime(1024));
120+
let n = &p * &q;
121+
let phi = (&p - 1u32) * (&q - 1u32);
122+
let e = BigUint::from(65537u32);
123+
let d = mod_inverse(&e, &phi).unwrap();
124+
(n, e, d)
151125
}
152126

153-
fn encrypt(message: u64, public_key: (u64, u64)) -> u64 {
154-
let (e, n) = public_key;
155-
mod_pow(message, e, n)
127+
fn encrypt(m: &BigUint, e: &BigUint, n: &BigUint) -> BigUint {
128+
m.modpow(e, n)
156129
}
157130

158-
fn decrypt(ciphertext: u64, private_key: (u64, u64)) -> u64 {
159-
let (d, n) = private_key;
160-
mod_pow(ciphertext, d, n)
131+
fn decrypt(c: &BigUint, d: &BigUint, n: &BigUint) -> BigUint {
132+
c.modpow(d, n)
161133
}
162134

163135
fn main() {
164-
let (public_key, private_key) = generate_keys();
165-
println!("Public Key (e, n): {:?}", public_key);
166-
println!("Private Key (d, n): {:?}", private_key);
136+
let (n, e, d) = generate_keypair();
137+
println!("Public key (n, e): ({}, {})", n, e);
138+
println!("Private key (d): {}", d);
167139

168-
let message = 42;
169-
println!("Original Message: {}", message);
140+
let message = BigUint::from(758562222222222343u64);
141+
println!("Original message: {}", message);
170142

171-
let encrypted = encrypt(message, public_key);
172-
println!("Encrypted: {}", encrypted);
143+
let encrypted = encrypt(&message, &e, &n);
144+
println!("Encrypted message: {}", encrypted);
173145

174-
let decrypted = decrypt(encrypted, private_key);
175-
println!("Decrypted: {}", decrypted);
146+
let decrypted = decrypt(&encrypted, &d, &n);
147+
println!("Decrypted message: {}", decrypted);
148+
149+
assert_eq!(message, decrypted);
176150
}
177151
```
178152

179153
### Notes
154+
This code implements the RSA (Rivest-Shamir-Adleman) cryptosystem, a widely used public-key cryptography algorithm. Here's a general outline of what the code does and how it works:
155+
156+
1. Import necessary libraries for big integer operations, random number generation, and parallel processing.
157+
2. Define constants and helper functions:
158+
- `SMALL_PRIMES`: A list of small prime numbers for initial primality checks.
159+
- `generate_prime`: Generates a prime number of a specified bit length.
160+
- `miller_rabin_test`: Implements the [[Miller-Rabin primality test]].
161+
- `is_prime`: Checks if a number is prime using small prime divisions and Miller-Rabin tests.
162+
- `mod_inverse`: Calculates the [[modular multiplicative inverse]].
163+
3. Implement core RSA functions:
164+
- `generate_keypair`: Generates RSA public and private keys.
165+
- `encrypt`: Encrypts a message using the public key.
166+
- `decrypt`: Decrypts a message using the private key.
167+
4. In the `main` function:
168+
- Generate a keypair (public and private keys).
169+
- Create a sample message.
170+
- Encrypt the message using the public key.
171+
- Decrypt the encrypted message using the private key.
172+
- Verify that the decrypted message matches the original.
173+
174+
The code uses parallel processing (via the `rayon` library) and optimized primality testing to improve performance. It also employs big integer arithmetic to handle the large numbers involved in RSA cryptography.
175+
180176
- [[Miller-Rabin primality test]] for numbers larger than 2^32
181177
- [[Modular exponentiation]] to calculate (base^exp) % modulus

content/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ If you'd like to follow along, you may navigate to [this Notion page](https://ww
2626
- [[RSA]]
2727

2828
#### EXERCISES
29-
- Implement naive RSA
29+
- [[RSA|Implement naive RSA]]
3030

3131
#### REFERENCES
3232
- [Lambda Week 1 Recommended Material](https://github.com/lambdaclass/sparkling_water_bootcamp)

0 commit comments

Comments
 (0)