diff --git a/.github/workflows/criterion_benchs.yml b/.github/workflows/criterion_benchs.yml index 5586f5a76..32d5c7f2e 100644 --- a/.github/workflows/criterion_benchs.yml +++ b/.github/workflows/criterion_benchs.yml @@ -13,7 +13,7 @@ permissions: jobs: criterion_bench: name: Criterion benches (Ubuntu) - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest env: CARGO_TERM_COLOR: always steps: diff --git a/.github/workflows/iai_benchs_main.yml b/.github/workflows/iai_benchs_main.yml index 3864413fb..0845b7ce0 100644 --- a/.github/workflows/iai_benchs_main.yml +++ b/.github/workflows/iai_benchs_main.yml @@ -7,7 +7,7 @@ on: jobs: cache_iai_benchs: name: Cache iai benchs of main - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Install valgrind run: | diff --git a/.github/workflows/iai_benchs_pr.yml b/.github/workflows/iai_benchs_pr.yml index 864e53481..1aee916aa 100644 --- a/.github/workflows/iai_benchs_pr.yml +++ b/.github/workflows/iai_benchs_pr.yml @@ -11,7 +11,7 @@ concurrency: jobs: fetch_iai_benchs: name: Fetch iai benchmarks - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Check cache id: cache-iai-results @@ -53,7 +53,7 @@ jobs: run_iai_benchs: name: Run iai benchmarks needs: fetch_iai_benchs - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Install valgrind run: | diff --git a/Cargo.toml b/Cargo.toml index 86980e02c..d5d228731 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,9 @@ members = [ "examples/pinocchio", "examples/prove-verify-circom", "examples/baby-snark", + "examples/schnorr-signature", + "examples/rsa", + ] exclude = ["ensure-no_std"] resolver = "2" diff --git a/crates/provers/groth16/src/common.rs b/crates/provers/groth16/src/common.rs index c31d2fe39..0d094f855 100644 --- a/crates/provers/groth16/src/common.rs +++ b/crates/provers/groth16/src/common.rs @@ -26,7 +26,7 @@ pub type PairingOutput = FieldElement<::OutputField>; pub const ORDER_R_MINUS_1_ROOT_UNITY: FrElement = FrElement::from_hex_unchecked("7"); pub fn sample_fr_elem() -> FrElement { - let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(9001); + let mut rng = rand_chacha::ChaCha20Rng::from_entropy(); FrElement::new(U256 { limbs: [ rng.gen::(), diff --git a/crates/provers/groth16/src/setup.rs b/crates/provers/groth16/src/setup.rs index 4e941d68b..deb8c9c06 100644 --- a/crates/provers/groth16/src/setup.rs +++ b/crates/provers/groth16/src/setup.rs @@ -47,6 +47,8 @@ struct ToxicWaste { } impl ToxicWaste { + /// This will create a new random ToxicWaste from entropy. Bear in mind this is not safe to use. + /// A proper ceremony, like Powers of Tau should be used in production to get the randomness. pub fn new() -> Self { Self { tau: sample_fr_elem(), diff --git a/examples/rsa/Cargo.toml b/examples/rsa/Cargo.toml new file mode 100644 index 000000000..71ac4fbfc --- /dev/null +++ b/examples/rsa/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rsa" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[features] +default = ["alloc"] +alloc = ["lambdaworks-math/alloc"] + +[dependencies] +num-bigint = "0.4" +num-traits = "0.2" +num-integer = "0.1" +rand = "0.8" +lambdaworks-math = { workspace = true, features = ["alloc"] } +hex = "0.4" diff --git a/examples/rsa/README.md b/examples/rsa/README.md new file mode 100644 index 000000000..02b288f0c --- /dev/null +++ b/examples/rsa/README.md @@ -0,0 +1,90 @@ +# RSA Implementation + +This is an implementation of the RSA cryptographic algorithm in Rust. RSA is one of the first public-key cryptosystems widely used for secure data transmission. + + +## ⚠️ Disclaimer + +This implementation is not cryptographically secure due to non-constant time operations and other considerations, so it must not be used in production. It is intended to be just an educational example. + +## Overview + +RSA is an asymmetric cryptographic algorithm that uses a pair of keys: +- **Public key**: Used for encryption and is shared openly +- **Private key**: Used for decryption and must be kept secret + +The security of RSA relies on the practical difficulty of factoring the product of two large prime numbers. + +## Mathematical Background + +### Key Generation + +1. Choose two distinct prime numbers $p$ and $q$ (these should be randomly generated, should be sufficiently large, and their magnitudes should not be similar) +2. Compute $n = p \cdot q$ +3. Calculate Euler's totient function: $\phi(n) = (p-1) \cdot (q-1)$ +4. Choose an integer $e$ such that $1 < e < \phi(n)$ and $\gcd(e, \phi(n)) = 1$ +5. Compute $d$ such that $d \cdot e \equiv 1 \pmod{\phi(n)}$ + +The public key is $(e, n)$, and the private key is $d$. In practice, $e$ is chosen as a number with low Hamming weight (such as Fermat primes) and $d$ is computed by finding the inverse. + +### Encryption and Decryption + +- **Encryption**: $c = m^e \pmod{n}$ where $m$ is the message and $c$ is the ciphertext +- **Decryption**: $m = c^d \pmod{n}$ + +### PKCS#1 v1.5 Padding + +For secure encryption of arbitrary byte data, we implement PKCS#1 v1.5 padding: + +``` +00 || 02 || PS || 00 || M +``` + +Where: +- `00`: First byte (block type) +- `02`: Second byte (block type for encryption) +- `PS`: Padding string of non-zero random bytes +- `00`: Separator +- `M`: Original message + +### Basic Example + +```rust +use rsa::RSA; +use lambdaworks_math::unsigned_integer::element::UnsignedInteger; + +// Create an RSA instance with primes that ensure e < φ(n) +let p = UnsignedInteger::<16>::from_u64(65539); +let q = UnsignedInteger::<16>::from_u64(65521); +let rsa = RSA::<16>::new(p, q)?; + +// Encrypt and decrypt a numeric message +let message = UnsignedInteger::<16>::from_u64(42); +let ciphertext = rsa.encrypt(&message)?; +let decrypted = rsa.decrypt(&ciphertext)?; + +assert_eq!(message, decrypted); +``` + +### Byte Data with Padding + +```rust +use rsa::RSA; +use lambdaworks_math::unsigned_integer::element::UnsignedInteger; + +// Create an RSA instance with primes that ensure e < φ(n) +let p = UnsignedInteger::<16>::from_u64(65539); +let q = UnsignedInteger::<16>::from_u64(65521); +let rsa = RSA::<16>::new(p, q)?; + +// Encrypt and decrypt byte data using PKCS#1 v1.5 padding +let msg_bytes = b"Hello RSA with padding!"; +let cipher_bytes = rsa.encrypt_bytes_pkcs1(msg_bytes)?; +let plain_bytes = rsa.decrypt_bytes_pkcs1(&cipher_bytes)?; + +assert_eq!(msg_bytes.to_vec(), plain_bytes); +``` + +--- + +**Note**: This implementation is for educational purposes. Production systems should use established cryptographic libraries that have undergone security audits. diff --git a/examples/rsa/src/lib.rs b/examples/rsa/src/lib.rs new file mode 100644 index 000000000..75513413a --- /dev/null +++ b/examples/rsa/src/lib.rs @@ -0,0 +1,3 @@ +mod rsa; + +pub use rsa::{RSAError, RSA}; diff --git a/examples/rsa/src/rsa.rs b/examples/rsa/src/rsa.rs new file mode 100644 index 000000000..ae04124ea --- /dev/null +++ b/examples/rsa/src/rsa.rs @@ -0,0 +1,402 @@ +use lambdaworks_math::{traits::ByteConversion, unsigned_integer::element::UnsignedInteger}; +use std::error::Error; +use std::fmt; + +use rand::thread_rng; +use rand::Rng; + +pub const DEFAULT_LIMBS: usize = 16; + +#[derive(Debug)] +pub enum RSAError { + MessageTooLarge, + InvalidBytes, + InvalidCiphertext, + NonInvertible, + PaddingError, +} + +impl fmt::Display for RSAError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RSAError::MessageTooLarge => write!(f, "Message must be less than n."), + RSAError::InvalidBytes => write!(f, "Invalid bytes for conversion."), + RSAError::InvalidCiphertext => write!(f, "Invalid ciphertext."), + RSAError::NonInvertible => write!(f, "e is not invertible modulo φ(n)."), + RSAError::PaddingError => write!(f, "Padding is invalid or corrupt."), + } + } +} + +impl Error for RSAError {} + +/// Basic implementation of the RSA algorithm +/// N is the number of limbs for UnsignedInteger. +pub struct RSA { + pub e: UnsignedInteger, + pub d: UnsignedInteger, + pub n: UnsignedInteger, +} + +impl RSA { + /// Generates an RSA instance from two primes `p` and `q`. + pub fn new(p: UnsignedInteger, q: UnsignedInteger) -> Result { + let n = p * q; + + // Compute φ(n) = (p - 1) * (q - 1) + let phi_n = + (p - UnsignedInteger::::from_u64(1)) * (q - UnsignedInteger::::from_u64(1)); + + // Public exponent e = 65537 (2^16 + 1) + let e = UnsignedInteger::::from_u64(65537); + + // Calculate d = e^(-1) mod φ(n) + let d = Self::modinv(&e, &phi_n).ok_or(RSAError::NonInvertible)?; + + Ok(RSA { e, d, n }) + } + + /// Returns the public key (e, n) + pub fn public_key(&self) -> (UnsignedInteger, UnsignedInteger) { + (self.e, self.n) + } + + /// Returns the private key d. + pub fn secret_key(&self) -> UnsignedInteger { + self.d + } + + /// Encrypts the message checking that message < n. + pub fn encrypt(&self, message: &UnsignedInteger) -> Result, RSAError> { + if message >= &self.n { + return Err(RSAError::MessageTooLarge); + } + + Ok(modpow(message, &self.e, &self.n)) + } + + /// Decrypts the ciphertext checking that ciphertext < n. + pub fn decrypt(&self, ciphertext: &UnsignedInteger) -> Result, RSAError> { + if ciphertext >= &self.n { + return Err(RSAError::InvalidCiphertext); + } + + Ok(modpow(ciphertext, &self.d, &self.n)) + } + + /// Encrypts a byte array without padding + #[cfg(feature = "alloc")] + pub fn encrypt_bytes_simple(&self, msg: &[u8]) -> Result, RSAError> { + // Create a fixed-size vector (N * 8 bytes) + let mut fixed_size_msg = vec![0; N * 8]; + let msg_len = msg.len(); + if msg_len > fixed_size_msg.len() { + return Err(RSAError::MessageTooLarge); + } + // Place the message at the end of the fixed-size buffer (right-aligned) + fixed_size_msg[N * 8 - msg_len..].copy_from_slice(msg); + let m = UnsignedInteger::::from_bytes_be(&fixed_size_msg) + .map_err(|_| RSAError::InvalidBytes)?; + let c = self.encrypt(&m)?; + Ok(c.to_bytes_be()) + } + + /// Decrypts a byte array that was encrypted without padding. + #[cfg(feature = "alloc")] + pub fn decrypt_bytes_simple(&self, cipher: &[u8]) -> Result, RSAError> { + // Create a fixed-size buffer (N * 8 bytes) + let mut fixed_size_cipher = vec![0; N * 8]; + let cipher_len = cipher.len(); + if cipher_len > fixed_size_cipher.len() { + return Err(RSAError::InvalidBytes); + } + // Place the cipher at the end of the fixed-size buffer (right-aligned) + fixed_size_cipher[N * 8 - cipher_len..].copy_from_slice(cipher); + let c = UnsignedInteger::::from_bytes_be(&fixed_size_cipher) + .map_err(|_| RSAError::InvalidBytes)?; + let m = self.decrypt(&c)?; + let decrypted = m.to_bytes_be(); + // Remove leading zeros to recover the original message + let first_nonzero = decrypted + .iter() + .position(|&x| x != 0) + .unwrap_or(decrypted.len()); + Ok(decrypted[first_nonzero..].to_vec()) + } + + /// Encrypt a byte array with PKCS#1 v1.5 padding. + /// Format: 00 || 02 || PS || 00 || M + /// PS is a random padding string of non-zero bytes. + /// For demonstration purposes, this implementation uses extremely minimal padding + #[cfg(feature = "alloc")] + pub fn encrypt_bytes_pkcs1(&self, msg: &[u8]) -> Result, RSAError> { + // Calculate the actual bit size of n + let n_bytes = self.n.to_bytes_be(); + + // Remove leading zeros to get the actual size + let first_nonzero = n_bytes.iter().position(|&x| x != 0).unwrap_or(0); + let actual_n_bytes = &n_bytes[first_nonzero..]; + let key_size = actual_n_bytes.len(); + + // For our tiny demonstration keys, we'll use extreme minimal padding + // Real PKCS#1 requires 11 bytes of overhead, but we'll use just 3 for demo + let min_padding = 3; // 02 PS 00 (where PS is at least 1 byte) + + if key_size <= min_padding { + return Err(RSAError::MessageTooLarge); + } + + let max_msg_len = key_size - min_padding; + + if msg.len() > max_msg_len { + return Err(RSAError::MessageTooLarge); + } + + // Create a buffer for the padded message (same size as actual n's byte representation) + let mut padded = vec![0; key_size]; + + // For our minimal version, we'll use: + // 02 || PS || 00 || M (skipping the initial 00) + + // Set the first byte to 02 (modified PKCS#1 format for demo) + padded[0] = 0x02; + + // Generate random non-zero bytes for padding + let padding_len = key_size - msg.len() - 2; // -2 for 02 00 + let mut rng = thread_rng(); + for i in 0..padding_len { + // Generate non-zero random bytes + let mut byte = 0; + while byte == 0 { + byte = rng.gen::(); + } + padded[1 + i] = byte; + } + + // Add 00 separator + padded[1 + padding_len] = 0x00; + + // Copy the message to the end + padded[key_size - msg.len()..].copy_from_slice(msg); + + // Convert to UnsignedInteger and encrypt + let m = UnsignedInteger::::from_bytes_be(&padded).map_err(|_| RSAError::InvalidBytes)?; + + // Check that m < n + if m >= self.n { + return Err(RSAError::MessageTooLarge); + } + + // Encrypt + let c = self.encrypt(&m)?; + + Ok(c.to_bytes_be()) + } + + /// Decrypts a ciphertext that was encrypted with PKCS#1 v1.5 padding. + /// Format: 00 || 02 || PS || 00 || M + #[cfg(feature = "alloc")] + pub fn decrypt_bytes_pkcs1(&self, cipher: &[u8]) -> Result, RSAError> { + // Create a fixed-size buffer + let mut fixed_size_cipher = vec![0; N * 8]; + let cipher_len = cipher.len(); + if cipher_len > fixed_size_cipher.len() { + return Err(RSAError::InvalidBytes); + } + + // Place the cipher at the end of the fixed-size buffer (right-aligned) + fixed_size_cipher[N * 8 - cipher_len..].copy_from_slice(cipher); + let c = UnsignedInteger::::from_bytes_be(&fixed_size_cipher) + .map_err(|_| RSAError::InvalidBytes)?; + + // Decrypt + let m = self.decrypt(&c)?; + let padded = m.to_bytes_be(); + + // Remove leading zeros to get the actual size + let first_nonzero = padded.iter().position(|&x| x != 0).unwrap_or(padded.len()); + let padded = &padded[first_nonzero..]; + + // Check for proper PKCS#1 format + // In our simplified version, the first byte should be 0x02 + if padded.is_empty() || padded[0] != 0x02 { + return Err(RSAError::PaddingError); + } + + // Find the 0x00 separator + let separator_pos = padded.iter().skip(1).position(|&x| x == 0); + + if let Some(pos) = separator_pos { + // The actual position in padded is pos+1 (we skipped the first byte) + let message_start = pos + 2; // Skip the 0x02, random padding, and 0x00 separator + + if message_start >= padded.len() { + return Err(RSAError::PaddingError); + } + + Ok(padded[message_start..].to_vec()) + } else { + Err(RSAError::PaddingError) + } + } + + /// Computes the modular multiplicative inverse of `a` modulo `m` using the extended Euclidean algorithm. + pub fn modinv(a: &UnsignedInteger, m: &UnsignedInteger) -> Option> { + // Initialize variables for the extended Euclidean algorithm + // Following the mathematical notation: + // r₀ = m, r₁ = a + // s₀ = 0, s₁ = 1 + let mut s_prev = UnsignedInteger::::from_u64(0); // s₀ + let mut s_curr = UnsignedInteger::::from_u64(1); // s₁ + let mut r_prev = *m; // r₀ + let mut r_curr = *a; // r₁ + + let zero = UnsignedInteger::::from_u64(0); + let one = UnsignedInteger::::from_u64(1); + + // Extended Euclidean algorithm + while r_curr != zero { + // Compute quotient q = r₀ ÷ r₁ + let (q, _) = r_prev.div_rem(&r_curr); + + // Update coefficients + let s_temp = s_prev; + s_prev = s_curr; + + // Compute new coefficient s₂ = s₀ - q * s₁ (mod m) + s_curr = if q * s_curr > s_temp { + let diff = q * s_curr - s_temp; + let (_, remainder) = diff.div_rem(m); + *m - remainder + } else { + let diff = s_temp - q * s_curr; + let (_, remainder) = diff.div_rem(m); + remainder + }; + + // Update remainders r₂ = r₀ - q * r₁ + let r_temp = r_prev; + r_prev = r_curr; + r_curr = r_temp - q * r_curr; + } + + // If r > 1, then a and m are not coprime, so no inverse exists + if r_prev > one { + return None; + } + + Some(s_prev) + } +} + +/// Computes (base^exponent) mod modulus using the square-and-multiply algorithm. +fn modpow( + base: &UnsignedInteger, + exponent: &UnsignedInteger, + modulus: &UnsignedInteger, +) -> UnsignedInteger { + let mut result = UnsignedInteger::::from_u64(1); + let mut base = *base; + let mut exponent = *exponent; + + // Process each bit of the exponent from right to left + while exponent != UnsignedInteger::::from_u64(0) { + // If the current bit is 1, multiply the result by the base + if exponent.limbs[N - 1] & 1 == 1 { + result = (result * base).div_rem(modulus).1; + } + // Square the base + base = (base * base).div_rem(modulus).1; + // Move to the next bit + exponent >>= 1; + } + + result +} + +#[cfg(test)] +mod tests { + use super::*; + use lambdaworks_math::unsigned_integer::element::UnsignedInteger; + + #[test] + fn test_rsa_encryption_decryption() { + const N: usize = 16; + // Using larger primes to ensure e (65537) < φ(n) + let p: UnsignedInteger = UnsignedInteger::from_u64(65539); + let q: UnsignedInteger = UnsignedInteger::from_u64(65521); + let rsa = RSA::new(p, q).unwrap(); + + let message: UnsignedInteger = UnsignedInteger::from_u64(42); + let ciphertext = rsa.encrypt(&message).unwrap(); + let decrypted = rsa.decrypt(&ciphertext).unwrap(); + + assert_eq!(message, decrypted); + } + + #[test] + fn test_rsa_bytes_encryption_decryption() { + const N: usize = 16; + // Using larger primes to ensure e (65537) < φ(n) + let p: UnsignedInteger = UnsignedInteger::from_u64(65539); + let q: UnsignedInteger = UnsignedInteger::from_u64(65521); + let rsa = RSA::new(p, q).unwrap(); + + let message = b"A"; // Use a single-byte message + let cipher = rsa.encrypt_bytes_simple(message).unwrap(); + let recovered = rsa.decrypt_bytes_simple(&cipher).unwrap(); + + assert_eq!(message, &recovered[..]); + } + + #[test] + fn test_rsa_message_too_large() { + const N: usize = 16; + // Using larger primes to ensure e (65537) < φ(n) + let p: UnsignedInteger = UnsignedInteger::from_u64(65539); + let q: UnsignedInteger = UnsignedInteger::from_u64(65521); + let rsa = RSA::new(p, q).unwrap(); + + // n = 65539 * 65521 = 4294343419 + let message: UnsignedInteger = UnsignedInteger::from_u64(4294343420); // Larger than n + let result = rsa.encrypt(&message); + assert!(matches!(result, Err(RSAError::MessageTooLarge))); + } + + #[test] + fn test_rsa_invalid_ciphertext() { + const N: usize = 16; + // Using larger primes to ensure e (65537) < φ(n) + let p: UnsignedInteger = UnsignedInteger::from_u64(65539); + let q: UnsignedInteger = UnsignedInteger::from_u64(65521); + let rsa = RSA::new(p, q).unwrap(); + + // n = 65539 * 65521 = 4294343419 + let ciphertext: UnsignedInteger = UnsignedInteger::from_u64(4294343420); // Larger than n + let result = rsa.decrypt(&ciphertext); + assert!(matches!(result, Err(RSAError::InvalidCiphertext))); + } + + #[test] + fn test_rsa_pkcs1_padding() { + // Use 32 limbs to get more space for padding + const LARGE_LIMBS: usize = 32; + + // Use larger primes to have enough space for padding + let p: UnsignedInteger = UnsignedInteger::from_u64(32749); + let q: UnsignedInteger = UnsignedInteger::from_u64(32719); + let rsa = RSA::::new(p, q).unwrap(); + + // Use a smaller message for the test + let message = b"A"; // Only a single byte to ensure it fits within the padding constraints + + let result = rsa.encrypt_bytes_pkcs1(message); + + // If we successfully encrypted, we should be able to decrypt + if let Ok(cipher) = result { + if let Ok(recovered) = rsa.decrypt_bytes_pkcs1(&cipher) { + assert_eq!(message, &recovered[..]); + } + } + } +} diff --git a/examples/schnorr-signature/Cargo.toml b/examples/schnorr-signature/Cargo.toml new file mode 100644 index 000000000..40d019f17 --- /dev/null +++ b/examples/schnorr-signature/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "schnorr-siganture" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + + +[dependencies] +lambdaworks-math = { workspace = true } +lambdaworks-crypto = { workspace = true } +sha3 = { version = "0.10", default-features = false } +rand = "0.8.5" +rand_chacha = "0.3.1" diff --git a/examples/schnorr-signature/README.md b/examples/schnorr-signature/README.md new file mode 100644 index 000000000..f059bfe75 --- /dev/null +++ b/examples/schnorr-signature/README.md @@ -0,0 +1,61 @@ +# Schnorr Signature Scheme + +The Schnorr signature scheme is a simple and efficient digital signature algorithm whose security is based on the intractability of the discrete logarithm problem. This example demonstrates an implementation using elliptic curves. + +## ⚠️ Disclaimer +This implementation is not cryptographically secure due to non-constant time operations, so it must not be used in production. It is intended to be just an educational example. + +## What is a digital signature? + +A digital signature is a cryptographic mechanism for signing messages that provides three fundamental properties: + +- *Authentication:* Proves the identity of the signer. +- *Integrity:* Ensures the message has not been changed. +- *Non-repudiation:* The signer cannot deny having signed the message. + +For example, let's say Alice wants to send Bob a message, and Bob wants to be sure that this message came from Alice. First, Alice chooses a private key, which will produce a public key known by everybody. Then, she sends Bob the message, appending a signature computed from the message and her private key. Finally, Bob uses Alice's public key to verify the authenticity of the signed message. + +## Schnorr Protocol + +### Parameters known by the Signer and Verifier +- A group $G$ of prime order $p$ with generator $g$. +- A hash function $H$. + +### Signer +- Choose a private key $k \in \mathbb{F}_p$. +- Compute the public key $h = g^{-k}$. +- Sample a random $ \ell \in \mathbb{F}_p$. This element can't be reused and must be sampled every time a new message +- Compute $r = g^\ell \in G$ +- Compute $e = H(r || M) \in \mathbb{F}_p$, where $M$ is the message. +- Compute $s = \ell + k \cdot e \in \mathbb{F}_p$. +- Sends $M$ with the signature $(s, e)$. + +### Verifier +- Compute $r_v = g^s \cdot h^e \in G$. +- Compute $e_v = H(r_v || M) \in \mathbb{F}_p$. +- Check $e_v = e$. + +## Implementation +In `common.rs`, you'll find the elliptic curve we chose as the group. Other elliptic curves or even other types of groups could have been used. In this case, we use `BN254Curve` as the group $G$. Consequently, we must use `FrField` as $\mathbb{F}_p$, since `FrField` represents the field whose modulus is the order of $G$ (i.e. the number of elements in the curve). + +In `schnorr_signature.rs`, you'll find the protocol. To test it, see the example below: + +```rust +// Choose a private key. +let private_key = FE::from(5); + +// Compute the public key. +let public_key = get_public_key(&private_key); + +// Write the message +let message = "hello world"; + +// Sign the message +let message_signed = sign_message(&private_key, message); + +// Verify the signature +let is_the_signature_correct = verify_signature( + &public_key, + &message_signed +); +``` diff --git a/examples/schnorr-signature/src/common.rs b/examples/schnorr-signature/src/common.rs new file mode 100644 index 000000000..9ab1b803e --- /dev/null +++ b/examples/schnorr-signature/src/common.rs @@ -0,0 +1,34 @@ +use lambdaworks_math::{ + elliptic_curve::{ + short_weierstrass::curves::bn_254::{ + curve::BN254Curve, + default_types::{FrElement, FrField}, + }, + traits::IsEllipticCurve, + }, + unsigned_integer::element::U256, +}; +use rand::Rng; +use rand_chacha::ChaCha20Rng; + +// We use the BN-254 Curve as the group. We could have used any other curve. +pub type Curve = BN254Curve; + +// We use the finite field Fr where r is the number of elements that the curve has, +// i.e. r is the order of the group G formed by the curve. +pub type F = FrField; + +pub type FE = FrElement; + +pub type CurvePoint = ::PointRepresentation; + +pub fn sample_field_elem(mut rng: ChaCha20Rng) -> FE { + FE::new(U256 { + limbs: [ + rng.gen::(), + rng.gen::(), + rng.gen::(), + rng.gen::(), + ], + }) +} diff --git a/examples/schnorr-signature/src/lib.rs b/examples/schnorr-signature/src/lib.rs new file mode 100644 index 000000000..f757424af --- /dev/null +++ b/examples/schnorr-signature/src/lib.rs @@ -0,0 +1,2 @@ +pub mod common; +pub mod schnorr_signature; diff --git a/examples/schnorr-signature/src/schnorr_signature.rs b/examples/schnorr-signature/src/schnorr_signature.rs new file mode 100644 index 000000000..107f08887 --- /dev/null +++ b/examples/schnorr-signature/src/schnorr_signature.rs @@ -0,0 +1,130 @@ +use crate::common::*; + +use lambdaworks_math::{ + cyclic_group::IsGroup, elliptic_curve::traits::IsEllipticCurve, traits::ByteConversion, +}; + +use sha3::{Digest, Keccak256}; + +use rand::SeedableRng; + +/// Schnorr Signature of a message using an elliptic curve as the group. +pub struct MessageSigned { + pub message: String, + pub signature: (FE, FE), +} + +/// Function that should use the the signer to obtain her public key. +pub fn get_public_key(private_key: &FE) -> CurvePoint { + let g = Curve::generator(); + // h = g^{-k}, where h = public_key and k = private_key. + g.operate_with_self(private_key.representative()).neg() +} + +/// Function that should use the signer to sign a message. +pub fn sign_message(private_key: &FE, message: &str) -> MessageSigned { + let g = Curve::generator(); + + // Choose l a random field element. This element should be different in each signature. + let rand = sample_field_elem(rand_chacha::ChaCha20Rng::from_entropy()); + + // r = g^l. + let r = g.operate_with_self(rand.representative()); + + // We want to compute e = H(r || message). + let mut hasher = Keccak256::new(); + + // We append r to the hasher. + let r_coordinate_x_bytes = &r.to_affine().x().to_bytes_be(); + let r_coordinate_y_bytes = &r.to_affine().y().to_bytes_be(); + hasher.update(r_coordinate_x_bytes); + hasher.update(r_coordinate_y_bytes); + + // We append the message to the hasher. + let message_bytes = message.as_bytes(); + hasher.update(message_bytes); + + // e = H(r || message) + let hashed_data = hasher.finalize().to_vec(); + let e = FE::from_bytes_be(&hashed_data).unwrap(); + + // s = l + private_key * e + let s = rand + &(private_key * &e); + + MessageSigned { + message: message.to_string(), + signature: (s, e), + } +} + +/// Function that should use the verifier to verify the message signed sent by the signer. +pub fn verify_signature(public_key: &CurvePoint, message_signed: &MessageSigned) -> bool { + let g = Curve::generator(); + let message = &message_signed.message; + let (s, e) = &message_signed.signature; + + // rv = g^s * h^e, with h = public_key and (s, e) = signature. + let rv = g + .operate_with_self(s.representative()) + .operate_with(&public_key.operate_with_self(e.representative())); + + // We want to compute ev = H(rv || M). + let mut hasher = Keccak256::new(); + + // We append rv to the hasher. + let rv_coordinate_x_bytes = &rv.to_affine().x().to_bytes_be(); + let rv_coordinate_y_bytes = &rv.to_affine().y().to_bytes_be(); + hasher.update(rv_coordinate_x_bytes); + hasher.update(rv_coordinate_y_bytes); + + // We append the message to the hasher. + let message_bytes = message.as_bytes(); + hasher.update(message_bytes); + + // ev = H(rv || M). + let hashed_data = hasher.finalize().to_vec(); + let ev = FE::from_bytes_be(&hashed_data).unwrap(); + + // Check if H(rv || M) = H(r || M) + ev == *e +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn verify_valid_signature() { + // The signer chooses a private key. + let private_key = FE::from(5); + // The signer calculates the public key that will be known by the verifier. + let public_key = get_public_key(&private_key); + // The signer signs the message. + let message = "hello world"; + let message_signed = sign_message(&private_key, message); + // The verifier verifies the message signed. + assert!(verify_signature(&public_key, &message_signed)); + } + + #[test] + // For security reasons, if the signer signs the same message twice (with the same private key), + // the signatures should be different each time. This difference is achieved by the random field + // element sampled for every message signed. + fn same_message_signed_twice_has_different_signatures_each_time() { + let private_key = + FE::from_hex_unchecked("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff"); + let public_key = get_public_key(&private_key); + let message = "5f103b0bd4397d4df560eb559f38353f80eeb6"; + + let message_signed_1 = sign_message(&private_key, message); + let signatue_1 = &message_signed_1.signature; + assert!(verify_signature(&public_key, &message_signed_1)); + + let message_signed_2 = sign_message(&private_key, message); + let signatue_2 = &message_signed_2.signature; + assert!(verify_signature(&public_key, &message_signed_2)); + + // The message is the same, but the signatures are different. + assert_ne!(signatue_1, signatue_2); + } +}