Repository: ncitron/badcrypto
Branch: master
Commit: bfc370b67ce5
Files: 9
Total size: 24.7 KB
Directory structure:
gitextract_57l_y995/
├── .gitignore
├── Cargo.toml
├── LICENSE.md
├── README.md
└── src/
├── chacha/
│ ├── README.md
│ └── mod.rs
├── elgamal/
│ ├── README.md
│ └── mod.rs
└── lib.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/target
================================================
FILE: Cargo.toml
================================================
[package]
name = "badcrypto"
version = "0.1.0"
edition = "2021"
[dependencies]
eyre = "0.6.8"
hex = "0.4.3"
rand = "0.8.5"
curv-kzen = { version = "0.10.0", features = ["num-bigint"], default-features = false }
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2022 Noah Citron
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
## Learning Cryptography in Public
This repository serves to document me learning cryptography. Each Rust module implements a different cryptographic protocol and will have it's own readme describing the protocol and any interesting learnings I bumped into along the way.
## Warning
Do not use this in production. It is called badcrypto for a reason. Some of these protocols may be implemented incorrectly. Even when I do implement them correctly, there may be side channel attacks. I have made no effort to ensure that these algorithms are constant-time, which means some of them may be susceptible to timing attacks. If you use this, you will get wrecked. I won't even feel bad, the repo is literally called badcrypto. Just to reiterate, don't use this. Don't roll your own crypto. Don't let me roll your crypto for you.
## Contents
- [ElGamal](./src/elgamal)
- [ChaCha20](./src/chacha)
================================================
FILE: src/chacha/README.md
================================================
## ChaCha20
ChaCha20 is a stream cipher designed by Daniel J. Bernstein in 2008. It is an improvement on the Salsa20 cipher, which was also designed by Bernstein. The ChaCha cipher operates on a 64 byte block of data at a time. It uses a key, nonce, and block counter to generate a stream of pseudo-random bits, which are then XORed with the plaintext to produce the ciphertext.
## Pseudo Random Generators (PRGs)
The ChaCha cipher uses a pseudo-random generator (PRG) to produce a stream of pseudo-random bits. It is a deterministic algorithm that takes the key, nonce, and block counter as input and produces a sequence of bits that appears random, but which can be reproduced given the same inputs.
ChaCha holds data in a 4x4 matrix consisting of 16 32 bit words containing the following contents:
- The first row contains the constants "0x61707865", "0x3320646e", "0x79622d32", "0x6b206574" (ascii encoding of "expand 32-byte k")
- The second row contains the first 4 words of the key
- The third row contains the last 4 words of the key
- The fourth row contains the block counter (1 word) and the nonce (3 words)
More succinctly:
```math
\begin{bmatrix} c_1 & c_2 & c_3 & c_4 \\ k_1 & k_2 & k_3 & k_4 \\ k_5 & k_6 & k_7 & k_8 \\ b & n_1 & n_2 & n_3 \end{bmatrix}
```
Each iteration of the PRG produces a single 512 bit block of random data. The bit stream produced by the PRG is generated as needed, with the block counter being incremented and the algorithm being rerun to produce each successive block.
The nonce value is typically chosen at random. It must be unique for each message that is encrypted, because reusing the same nonce is effectively creating a "two-time pad". With a two-time pad, the attacker has access to the ciphertext of two messages that were encrypted with the same random stream. An attacker can XOR these two ciphertexts together to cancel out the random stream and produce the XOR of the two plaintexts. If the plaintexts have any common words or patterns, the attacker may be able to recover parts of the message.
## Encryption
To encrypt a message with the ChaCha cipher, the following steps are performed:
1. The key and nonce are used to initialize the PRG.
2. The PRG produces a stream of pseudo-random bits.
3. The pseudo-random bits are XORed with the plaintext to produce the ciphertext.
4. The sender sends both the ciphertext and the nonce to the receiver.
## Decryption
To decrypt a message that has been encrypted with the ChaCha cipher, the following steps are performed:
1. The key and nonce are used to initialize the PRG.
2. The PRG produces the same stream of pseudo-random bits that was used to encrypt the message.
3. The pseudo-random bits are XORed with the ciphertext to produce the plaintext.
## How It Works
The PRG in the ChaCha cipher operates by applying a series of operations to the input matrix. The basic building block for these operations is called the quarter-round, which takes in four 32-bit integers, and scrambles them by applying a series of XOR, bitwise rotation, and overflowing addition between the values. Here is the code for the quarter-round function we use in our implementation.
```rust
/// Quarter-round function as defined by section 2.1 of RFC 8439
fn quarter_round(a: &mut u32, b: &mut u32, c: &mut u32, d: &mut u32) {
*a = a.wrapping_add(*b);
*d ^= *a;
*d = rotl(*d, 16);
*c = c.wrapping_add(*d);
*b ^= *c;
*b = rotl(*b, 12);
*a = a.wrapping_add(*b);
*d ^= *a;
*d = rotl(*d, 8);
*c = c.wrapping_add(*d);
*b ^= *c;
*b = rotl(*b, 7);
}
/// Circular left shift. Panics if shift if greater than 32.
fn rotl(value: u32, shift: u32) -> u32 {
value << shift | value >> (32 - shift)
}
```
In order to sufficiently scramble the input matrix, ChaCha20 applies the quarter-round on each column of the matrix and then on each diagonal. These two steps combined are called the double-round, and are performed a total of 10 times in ChaCha20. Finally, we perform a element-wise overflowing addition to combine the scrambled matrix with the original input matrix. This makes the output noninvertable. After these operations we concatenate all elements of the matrix to produce the 512 bit block of pseudo-random bits.
The constants used in the ChaCha cipher are a set of fixed values that are included in the input to the PRG. They were added to the cipher to reduce the amount of attacker-controlled input. This has the effect of removing certain correlations between inputs that are maintained in the outputs, increasing security.
The constants used in the ChaCha cipher are also known as "nothing up my sleeve numbers." This phrase refers to the idea that the constants are chosen in a transparent and verifiable way, to ensure that they do not contain any hidden information or backdoors. In other words, the constants are chosen in a way that makes it clear that there is "nothing up my sleeve" – no hidden tricks or secrets.
## Security
The security of the ChaCha cipher relies on the PRG being able to produce a stream of bits that is difficult for an attacker to predict or distinguish from a truly random stream. If the PRG is predictable, then an attacker may be able to determine the output of the PRG and recover the plaintext of a message.
As far as we know, the double-round technique for scrambling the matrix is secure. The state of the art attacks against ChaCha can currently only break the first 7 of 20 quarter-rounds.
================================================
FILE: src/chacha/mod.rs
================================================
use eyre::Result;
use rand::random;
/// ChaCha20 encryption with 96 bit nonce and 32 bit counter
pub struct ChaCha20 {
key: Key,
}
/// ChaCha20 ciphertext
pub struct CipherText {
/// Variable length ciphertext bytes
pub c: Vec<u8>,
/// Nonce used to seed PRG state
pub nonce: Nonce,
}
/// 256 bit ChaCha private key
pub struct Key([u32; 8]);
/// 96 bit nonce used in PRG
pub struct Nonce([u32; 3]);
/// Internal ChaCha state representing the 4x4 u32 matrix
struct ChaChaState(Vec<u32>);
impl ChaCha20 {
/// Creates a new ChaCha cipher from a private key
pub fn new(key: Key) -> Self {
Self { key }
}
/// Encrypts a message
pub fn encrypt(&self, message: &str) -> CipherText {
let nonce = Nonce::new(random::<[u32; 3]>());
let message_bytes = message.as_bytes().to_vec();
let c = self.apply_cipher_with_nonce(&message_bytes, &nonce);
CipherText { c, nonce }
}
/// Decrypts a message. Errors if the message is not valid utf-8
pub fn decrypt(&self, ciphertext: &CipherText) -> Result<String> {
let message_bytes = self.apply_cipher_with_nonce(&ciphertext.c, &ciphertext.nonce);
Ok(String::from_utf8(message_bytes)?)
}
/// Applies cipher to the message with a given nonce. Can be used to encrypt
/// or decrypt. Nonce must be unique. If a nonce is reused, messages may be
/// decrypted.
fn apply_cipher_with_nonce(&self, message: &Vec<u8>, nonce: &Nonce) -> Vec<u8> {
let counter_start = 1u32;
let c = message
.chunks(64)
.enumerate()
.map(|(i, chunk)| {
let j = counter_start + i as u32;
let mut state = ChaChaState::new(&self.key, j, &nonce);
state.chacha_block();
let key_stream = state
.0
.iter()
.flat_map(|n| n.to_le_bytes())
.collect::<Vec<u8>>();
key_stream
.iter()
.zip(chunk)
.map(|(key_byte, message_byte)| key_byte ^ message_byte)
.collect::<Vec<u8>>()
})
.flatten()
.collect::<Vec<u8>>();
c
}
}
impl ChaChaState {
/// Creates a new ChaChaState
pub fn new(key: &Key, counter: u32, nonce: &Nonce) -> Self {
let constants = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
let mut block = Vec::<u32>::new();
block.extend(constants);
block.extend(key.0);
block.push(counter);
block.extend(nonce.0);
Self(block)
}
/// Runs the ChaCha PRG function to generate a random block
pub fn chacha_block(&mut self) {
let original = self.0.clone();
for _ in 0..10 {
// column round
self.quarter_round_state(0, 4, 8, 12);
self.quarter_round_state(1, 5, 9, 13);
self.quarter_round_state(2, 6, 10, 14);
self.quarter_round_state(3, 7, 11, 15);
// diagonal round
self.quarter_round_state(0, 5, 10, 15);
self.quarter_round_state(1, 6, 11, 12);
self.quarter_round_state(2, 7, 8, 13);
self.quarter_round_state(3, 4, 9, 14);
}
for i in 0..16 {
self.0[i] = self.0[i].wrapping_add(original[i]);
}
}
/// Applies the quarter round function with the given state idices
fn quarter_round_state(&mut self, ai: usize, bi: usize, ci: usize, di: usize) {
let mut a = self.0[ai];
let mut b = self.0[bi];
let mut c = self.0[ci];
let mut d = self.0[di];
quarter_round(&mut a, &mut b, &mut c, &mut d);
self.0[ai] = a;
self.0[bi] = b;
self.0[ci] = c;
self.0[di] = d;
}
}
impl Key {
/// Create a new Key. Errors if the string is not a 32 byte hex value.
pub fn new(s: &str) -> Result<Self> {
let key: [u32; 8] = hex::decode(s)
.map_err(|_| eyre::eyre!("cannot parse key"))?
.chunks(4)
.map(|chunk| Ok(u32::from_le_bytes(chunk.try_into()?)))
.collect::<Result<Vec<u32>>>()
.map_err(|_| eyre::eyre!("cannot parse key"))?
.try_into()
.map_err(|_| eyre::eyre!("cannot parse key"))?;
Ok(Self(key))
}
}
impl Nonce {
/// Create a new Nonce from a string. Fails if the string is not a 12 byte hex value.
pub fn from_str(s: &str) -> Result<Self> {
let nonce: [u32; 3] = hex::decode(s)
.map_err(|_| eyre::eyre!("cannot parse nonce"))?
.chunks(4)
.map(|chunk| Ok(u32::from_le_bytes(chunk.try_into()?)))
.collect::<Result<Vec<u32>>>()
.map_err(|_| eyre::eyre!("cannot parse nonce"))?
.try_into()
.map_err(|_| eyre::eyre!("cannot parse nonce"))?;
Ok(Self(nonce))
}
/// Creates a new nonce from a fixed length u32 array
pub fn new(n: [u32; 3]) -> Self {
Self(n)
}
}
/// Quarter-round function as defined by section 2.1 of RFC 8439
fn quarter_round(a: &mut u32, b: &mut u32, c: &mut u32, d: &mut u32) {
*a = a.wrapping_add(*b);
*d ^= *a;
*d = rotl(*d, 16);
*c = c.wrapping_add(*d);
*b ^= *c;
*b = rotl(*b, 12);
*a = a.wrapping_add(*b);
*d ^= *a;
*d = rotl(*d, 8);
*c = c.wrapping_add(*d);
*b ^= *c;
*b = rotl(*b, 7);
}
/// Circular left shift. Panics if shift if greater than 32.
fn rotl(value: u32, shift: u32) -> u32 {
value << shift | value >> (32 - shift)
}
#[test]
fn test_full_cycle() {
let message = "Hello, World!";
let key = Key::new(&hex::encode(random::<[u8; 32]>())).unwrap();
let cipher = ChaCha20::new(key);
let ciphertext = cipher.encrypt(message);
let decrypted_message = cipher.decrypt(&ciphertext).unwrap();
assert_eq!(decrypted_message, message);
}
#[test]
fn test_apply_cipher() {
// test vector from RFC 8439
let key = Key::new("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap();
let nonce = "000000000000004a00000000";
let message = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
let nonce = Nonce::from_str(nonce).unwrap();
let k = ChaCha20::new(key);
let c = k.apply_cipher_with_nonce(&message.as_bytes().to_vec(), &nonce);
let expected = "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0bf91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab77937365af90bbf74a35be6b40b8eedf2785e42874d";
assert_eq!(c, hex::decode(expected).unwrap());
}
#[test]
fn test_chacha_block() {
// test vector from RFC 8439
let key = Key::new("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap();
let nonce = Nonce::from_str("000000090000004a00000000").unwrap();
let counter = 1;
let mut state = ChaChaState::new(&key, counter, &nonce);
state.chacha_block();
let block = hex::encode(
state
.0
.iter()
.flat_map(|chunk| chunk.to_le_bytes())
.collect::<Vec<_>>(),
);
let expected_block = "10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4ed2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e";
assert_eq!(block, expected_block);
}
#[test]
fn test_quarter_round() {
// test vector from RFC 8439
let mut a = 0x11111111;
let mut b = 0x01020304;
let mut c = 0x9b8d6f43;
let mut d = 0x01234567;
quarter_round(&mut a, &mut b, &mut c, &mut d);
assert_eq!(a, 0xea2a92f4);
assert_eq!(b, 0xcb1cf8ce);
assert_eq!(c, 0x4581472e);
assert_eq!(d, 0x5881c4bb);
}
================================================
FILE: src/elgamal/README.md
================================================
## ElGamal
ElGamal encryption is a public-key encryption scheme that is based on the difficulty of finding discrete logarithms. It is less commonly used as it is less secure than other methods, and is vulnerable to chosen ciphertext attacks. It does however have some interesting properties, such as multiplicative homomorphism.
The elliptic curve variant of ElGamal encryption uses elliptic curve cryptography (ECC) instead of the traditional integer-based modular arithmetic used in the original scheme.
One advantage of the elliptic curve variant of ElGamal encryption is that it is more efficient than the original scheme, due to the faster point operations in ECC. It is also more secure, since the underlying elliptic curve is more difficult to break than modular arithmetic. However, the security of the elliptic curve variant of ElGamal encryption depends on the choice of the elliptic curve and the size of the key, so it is important to carefully select these parameters to ensure the desired level of security.
## Key Generation
To generate a key, first select a random number for the secret key $s$. The public key $P$ is calculated by multiplying the secret key by our curve's generator point.
$$ P = s \cdot G $$
## Encryption
ElGamal encryption can only encrypt messages that are valid points on the elliptic curve. Later in this document we will describe a mechanism to embed an arbitrary integer inside of a curve point to create a more practical encryption scheme. We are going to encrypt some point $M$ using public key $P$. The ciphertext in ElGamal is actually two curve points, denoted as $C_1$ and $C_2$. First, we must generate some random scalar $r$. We can then encrypt the point using the function below.
$$ \mathrm{enc_{P}}(M) = (C_1, C_2)= (r \cdot G, r \cdot P + M) $$
## Decryption
To recover the encrypted point using the secret key, use the below function.
$$ \mathrm{dec_{s}}(C_1, C_2) = C_2 - s \cdot C_1 $$
## How it Works
We can actually show that ElGamal decryption is in fact the inverse of encryption with simple algebra.
$$ \mathrm{dec_{s}}(\mathrm{enc_{P}}(M)) = (r \cdot P + M) - (s \cdot r \cdot G) = (r \cdot s \cdot G + M) - (s \cdot r \cdot G) = M $$
So now we have some intuition as to how recovering the original point works. However, how is secrecy preserved? This property relies on something called the discrete log problem for elliptical curves. We will only discuss the discrete log problem for elliptical curves here, but it originally is used in the context of prime fields.
The discrete log problem (for elliptical curves) is to find some scalar $k$ such that multiplying point $P$ times $k$ yields point $Q$.
$$ P \cdot k = Q $$
It turns out that given $P$ and $Q$, finding $k$ is extremely difficult.
If we take a look at our $\mathrm{enc}$ function, you can see that even though our adversaries know $C_1$ and $G$, it will still be computationally difficult to recover $r$. Since $r$ cannot be recovered, there is no way to subtract $r \cdot P$ from $C_2$ to yield $M$.
Fortunately for the receiver of the message, $r \cdot P$ can easily be recovered by multiplying the secret key $s$ by $C_1$. Given that $C_2$ is just equal to $r \cdot P + M$, we just need to subtract our recovered $r \cdot P$ from $C_2$ to yield the original message $M$.
## Encrypting Arbitrary Messages
We noted earlier that the message $M$ to be encrypted must be a valid point on the curve. However, we often want to send messages that contain arbitrary data. To do this, we need a way to convert any integer $n$ into a curve point $P$. This method is based on [this paper](https://arxiv.org/pdf/1707.04892.pdf) by Ahmad Steef, A. Alkhatib, and M. N. Shamma. It is actually quite simple.
Select some integer value $k$. The larger $k$ is, the greater the likelyhood we can find a good embedding for $n$. However, as $k$ increases in size, the maximum size $n$ that can be embedded in a single point decreases.
To calculate the x value of our point, use the below equation with $i$ starting at 0.
$$ x = k \cdot n + i $$
Then, attempt to calculate the $y$ coordinate on the curve that corresponds to the $x$ coordinate. This may not be possible since not every $x$ value is on the curve. If there is no valid $y$, simply increment $i$ and try again.
To recover the message $n$ from the point's $x$ coordinate, use the equation below.
$$ n = \frac{x}{k} $$
As long as $i < k$, this method should decode the correct message. If $i \geq k$, then it is impossible to differentiate between $n$ and $n + 1$. This is why increasing the size of $k$ lowers the failure rate.
================================================
FILE: src/elgamal/mod.rs
================================================
use std::str::from_utf8;
use curv::{
arithmetic::{BasicOps, Converter, Modulo, Primes, Zero},
elliptic::curves::{Point, Scalar, Secp256k1},
BigInt,
};
use eyre::Result;
/// An ElGamal public key using the secp256k1 curve
pub struct PubKey {
point: Point<Secp256k1>,
}
/// An ElGamal private key using the secp256k1 curve
pub struct SecretKey {
secret: Scalar<Secp256k1>,
}
/// An encypted ciphertext
#[derive(Debug)]
pub struct CipherText {
c1: Point<Secp256k1>,
c2: Point<Secp256k1>,
}
impl SecretKey {
/// Generates a new private key using a random seed
pub fn new() -> Self {
Self {
secret: Scalar::random(),
}
}
/// Decryps a CipherText into a String
pub fn decrypt_point(&self, c: &CipherText) -> Point<Secp256k1> {
let s = self.secret.clone() * &c.c1;
&c.c2 - s
}
/// Decrypts a CipherText into a String. Fails if point does not
/// decrypt into a valid utf-8 string.
pub fn decrypt_message(&self, c: &CipherText) -> Result<String> {
let point = self.decrypt_point(c);
let bytes = unembed(&point).to_bytes();
from_utf8(&bytes)
.map_err(|_| eyre::eyre!("cannot decode point"))
.map(|s| s.to_string())
}
}
impl PubKey {
/// Encypts a Point into a CipherText
pub fn encrypt_point(&self, m: &Point<Secp256k1>) -> CipherText {
let r = Scalar::random();
let c1 = r.clone() * Point::generator();
let c2 = r * &self.point + m;
CipherText { c1, c2 }
}
/// Encypts a message into a CipgerText. Fails if message is
/// too long to fit into the curve point.
pub fn encrypt_message(&self, m: &str) -> Result<CipherText> {
let m = embed(&BigInt::from_bytes(m.as_bytes()))?;
Ok(self.encrypt_point(&m))
}
}
impl From<&SecretKey> for PubKey {
/// Converts a private key into a public key
fn from(sk: &SecretKey) -> Self {
let point = sk.secret.clone() * Point::generator();
Self { point }
}
}
/// Encodes a BigInt message as a Point. Fails if message is too long
/// or if no valid encoding can be found the message.
/// Method based on https://arxiv.org/pdf/1707.04892.pdf
fn embed(m: &BigInt) -> Result<Point<Secp256k1>> {
let k = 30;
let n = BigInt::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F")
.unwrap();
if (m + 1) * k >= n {
eyre::bail!("message too long");
}
for i in 0..k {
let x = m * k + i;
let y_squared = (x.pow(3) + 7) % n.clone();
let y = sqrt_mod_p(&y_squared, &n)?;
if let Some(y) = y {
return Point::from_coords(&x, &y).map_err(|_| eyre::eyre!("cannot embed message"));
}
}
Err(eyre::eyre!("cannot embed message"))
}
/// Decodes a Point into its origianl BigInt message
/// Method based on https://arxiv.org/pdf/1707.04892.pdf
fn unembed(p: &Point<Secp256k1>) -> BigInt {
let k = 30;
p.x_coord().unwrap() / BigInt::from(k)
}
/// Tonelli-Shanks algorithm for finding a square root in a prime field.
/// If there is no square root None is returned. Fails if p is composite.
fn sqrt_mod_p(n: &BigInt, p: &BigInt) -> Result<Option<BigInt>> {
let ls = |x: &BigInt| -> BigInt { BigInt::mod_pow(x, &((p - 1) / 2), p) };
if !Primes::is_probable_prime(p, 64) {
eyre::bail!("p is composite")
}
if ls(n) != BigInt::from(1) {
return Ok(None);
}
let mut q = p - 1;
let mut s = BigInt::zero();
while &q & BigInt::from(1) == BigInt::zero() {
s += 1;
q >>= 1;
}
if s == BigInt::from(1) {
return Ok(Some(BigInt::mod_pow(n, &((p + 1) / 4), p)));
}
let mut z = BigInt::from(2);
while ls(&z) != p - 1 {
z += 1;
}
let mut c = BigInt::mod_pow(&z, &q, p);
let mut r = BigInt::mod_pow(n, &((&q + 1) / 2), p);
let mut t = BigInt::mod_pow(n, &q, p);
let mut m = s;
loop {
if t == BigInt::from(1) {
return Ok(Some(r));
}
let mut i = BigInt::zero();
let mut z = t.clone();
while z != BigInt::from(1) && i < &m - 1 {
z = z.pow(2) % p;
i += 1;
}
let mut b = c.clone();
let e = &m - &i - 1;
while e > BigInt::zero() {
b = b.pow(2) % p;
}
r = r * &b % p;
c = &b * &b % p;
t = t * &c % p;
m = i;
}
}
#[test]
fn test_encrypt_message() {
let sk = SecretKey::new();
let pk = PubKey::from(&sk);
let message = "Hello, World!".to_string();
let enc = pk.encrypt_message(&message).unwrap();
let message_dec = sk.decrypt_message(&enc).unwrap();
assert_eq!(message_dec, message);
}
#[test]
fn test_encrypt_point() {
let sk = SecretKey::new();
let pk = PubKey::from(&sk);
let point = Scalar::from(5) * Point::generator();
let enc = pk.encrypt_point(&point);
let point_dec = sk.decrypt_point(&enc);
assert_eq!(point_dec, point);
}
================================================
FILE: src/lib.rs
================================================
pub mod chacha;
pub mod elgamal;
gitextract_57l_y995/
├── .gitignore
├── Cargo.toml
├── LICENSE.md
├── README.md
└── src/
├── chacha/
│ ├── README.md
│ └── mod.rs
├── elgamal/
│ ├── README.md
│ └── mod.rs
└── lib.rs
SYMBOL INDEX (35 symbols across 2 files)
FILE: src/chacha/mod.rs
type ChaCha20 (line 5) | pub struct ChaCha20 {
method new (line 28) | pub fn new(key: Key) -> Self {
method encrypt (line 33) | pub fn encrypt(&self, message: &str) -> CipherText {
method decrypt (line 42) | pub fn decrypt(&self, ciphertext: &CipherText) -> Result<String> {
method apply_cipher_with_nonce (line 50) | fn apply_cipher_with_nonce(&self, message: &Vec<u8>, nonce: &Nonce) ->...
type CipherText (line 10) | pub struct CipherText {
type Key (line 18) | pub struct Key([u32; 8]);
method new (line 135) | pub fn new(s: &str) -> Result<Self> {
type Nonce (line 21) | pub struct Nonce([u32; 3]);
method from_str (line 151) | pub fn from_str(s: &str) -> Result<Self> {
method new (line 165) | pub fn new(n: [u32; 3]) -> Self {
type ChaChaState (line 24) | struct ChaChaState(Vec<u32>);
method new (line 82) | pub fn new(key: &Key, counter: u32, nonce: &Nonce) -> Self {
method chacha_block (line 95) | pub fn chacha_block(&mut self) {
method quarter_round_state (line 118) | fn quarter_round_state(&mut self, ai: usize, bi: usize, ci: usize, di:...
function quarter_round (line 171) | fn quarter_round(a: &mut u32, b: &mut u32, c: &mut u32, d: &mut u32) {
function rotl (line 190) | fn rotl(value: u32, shift: u32) -> u32 {
function test_full_cycle (line 195) | fn test_full_cycle() {
function test_apply_cipher (line 207) | fn test_apply_cipher() {
function test_chacha_block (line 225) | fn test_chacha_block() {
function test_quarter_round (line 248) | fn test_quarter_round() {
FILE: src/elgamal/mod.rs
type PubKey (line 11) | pub struct PubKey {
method encrypt_point (line 54) | pub fn encrypt_point(&self, m: &Point<Secp256k1>) -> CipherText {
method encrypt_message (line 64) | pub fn encrypt_message(&self, m: &str) -> Result<CipherText> {
method from (line 72) | fn from(sk: &SecretKey) -> Self {
type SecretKey (line 16) | pub struct SecretKey {
method new (line 29) | pub fn new() -> Self {
method decrypt_point (line 36) | pub fn decrypt_point(&self, c: &CipherText) -> Point<Secp256k1> {
method decrypt_message (line 43) | pub fn decrypt_message(&self, c: &CipherText) -> Result<String> {
type CipherText (line 22) | pub struct CipherText {
function embed (line 81) | fn embed(m: &BigInt) -> Result<Point<Secp256k1>> {
function unembed (line 106) | fn unembed(p: &Point<Secp256k1>) -> BigInt {
function sqrt_mod_p (line 113) | fn sqrt_mod_p(n: &BigInt, p: &BigInt) -> Result<Option<BigInt>> {
function test_encrypt_message (line 172) | fn test_encrypt_message() {
function test_encrypt_point (line 185) | fn test_encrypt_point() {
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (26K chars).
[
{
"path": ".gitignore",
"chars": 9,
"preview": "/target\n\n"
},
{
"path": "Cargo.toml",
"chars": 212,
"preview": "[package]\nname = \"badcrypto\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\neyre = \"0.6.8\"\nhex = \"0.4.3\"\nrand = \"0.8"
},
{
"path": "LICENSE.md",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2022 Noah Citron\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 892,
"preview": "## Learning Cryptography in Public\nThis repository serves to document me learning cryptography. Each Rust module impleme"
},
{
"path": "src/chacha/README.md",
"chars": 5492,
"preview": "## ChaCha20\n\nChaCha20 is a stream cipher designed by Daniel J. Bernstein in 2008. It is an improvement on the Salsa20 ci"
},
{
"path": "src/chacha/mod.rs",
"chars": 7874,
"preview": "use eyre::Result;\nuse rand::random;\n\n/// ChaCha20 encryption with 96 bit nonce and 32 bit counter\npub struct ChaCha20 {\n"
},
{
"path": "src/elgamal/README.md",
"chars": 4623,
"preview": "## ElGamal\nElGamal encryption is a public-key encryption scheme that is based on the difficulty of finding discrete loga"
},
{
"path": "src/elgamal/mod.rs",
"chars": 5066,
"preview": "use std::str::from_utf8;\n\nuse curv::{\n arithmetic::{BasicOps, Converter, Modulo, Primes, Zero},\n elliptic::curves:"
},
{
"path": "src/lib.rs",
"chars": 33,
"preview": "pub mod chacha;\npub mod elgamal;\n"
}
]
About this extraction
This page contains the full source code of the ncitron/badcrypto GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (24.7 KB), approximately 7.2k tokens, and a symbol index with 35 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.