diff --git a/content/Chinese Remainder Theorem.md b/content/Chinese Remainder Theorem.md index ed80b41..0493733 100644 --- a/content/Chinese Remainder Theorem.md +++ b/content/Chinese Remainder Theorem.md @@ -18,7 +18,7 @@ tags: [] ## Constructing the Solution: - Calculate $M=m_1*m_2*\dots *m_n$ - For each $i$, calculate $M_i=M/m_i c$ -- Find the modular multiplicative inverse of $M_i$ modulo $m_i$, call it $y_i$ ($M_i y_i \equiv 1 \text{ (mod } m_i \text{)}$) +- Find the [[modular multiplicative inverse]] of $M_i$ modulo $m_i$, call it $y_i$ ($M_i y_i \equiv 1 \text{ (mod } m_i \text{)}$) - The solution is: $x \equiv (a_1M_1y_1+a_2M_2y_2+\dots+a_nM_ny_n)$ (mod $M$). ## Why It Works diff --git a/content/Lagrange interpolation formula.md b/content/Lagrange interpolation formula.md new file mode 100644 index 0000000..317fda8 --- /dev/null +++ b/content/Lagrange interpolation formula.md @@ -0,0 +1,71 @@ +--- +id: 1725960857-lagrange-interpolation-formula +aliases: + - Lagrange interpolation formula +tags: [] +--- + +# Lagrange interpolation formula +A mathematical method used to reconstruct a polynomial given a set of points. In the context of Shamir's Secret Sharing, it's used to reconstruct the secret from the shared points. + +The general form of the Lagrange interpolation formula is: + +``` +f(x) = Σ(i=1 to n) yi * Li(x) +``` + +Where: +- `f(x)` is the reconstructed polynomial +- `yi` are the y-coordinates of the known points +- `Li(x)` are the Lagrange basis polynomials + +In the [[1725904360-shamirs-secret-sharing|Shamir's Secret Sharing]] code, this formula is implemented as follows: + +1. The outer loop `for i in 0..threshold` corresponds to the summation in the formula. + +2. The `Li(x)` term is calculated in parts: + ```rust + let mut numerator = BigInt::one(); + let mut denominator = BigInt::one(); + + for j in 0..threshold { + if i != j { + let (xj, _) = &shares[j]; + numerator *= xj.to_bigint().unwrap(); + numerator %= &prime_int; + denominator *= + (xj.to_bigint().unwrap() - xi.to_bigint().unwrap() + &prime_int) % &prime_int; + denominator %= &prime_int; + } + } + ``` + + This calculates: + ``` + Li(x) = Π(j≠i) (x - xj) / (xi - xj) + ``` + +3. The `yi` term is multiplied in: + ```rust + let term = yi.to_bigint().unwrap() * numerator * mod_inverse(&denominator, &prime_int); + ``` + +4. Each term is added to the secret: + ```rust + secret += term; + secret %= &prime_int; + ``` + +In [[Shamir's Secret Sharing]]: +- The secret is the constant term of a polynomial. +- Shares are points on this polynomial. +- Reconstruction uses Lagrange interpolation to find the polynomial and extract the constant term (the secret). + +The use of modular arithmetic (with a prime modulus) ensures: +1. The calculations are performed in a [[finite field]]. +2. Security properties of the scheme are maintained. +3. All operations "wrap around" the prime, keeping values manageable. + +This implementation is secure because: +- It requires a threshold number of shares to reconstruct. +- Without enough shares, the polynomial (and thus the secret) remains undetermined. diff --git a/content/Modular exponentiation.md b/content/Modular exponentiation.md index 0758b84..c392f8e 100644 --- a/content/Modular exponentiation.md +++ b/content/Modular exponentiation.md @@ -8,49 +8,47 @@ tags: [] # Modular exponentiation Efficient algorithm to calculate (base^exp) % modulus. +The key idea is to break down the exponent into its binary representation and use the property that: + +(a * b) mod m = ((a mod m) * (b mod m)) mod m + +This allows the algorithm to perform the modulus operation at each step, keeping the intermediate results small and manageable. + ## Rust Implementation ```rust -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 = mod_mul(result, base, modulus); +fn mod_pow(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint { + let mut result = BigUint::one(); + let mut base = base.clone(); + let mut exp = exp.clone(); + while exp > BigUint::zero() { + if exp.bit(0) { + result = (result * &base) % modulus; } - + base = (&base * &base) % modulus; exp >>= 1; - base = mod_mul(base, base, modulus); } result } - -fn mod_mul(a: u64, b: u64, m: u64) -> u64 { - ((a as u128 * b as u128) % m as u128) as u64 -} ``` -1. Function signature: - - Takes three `u64` parameters: `base`, `exp` (exponent), and `modulus` - - Returns a `u64` result - -2. Edge case handling: - - If `modulus` is 1, it returns 0 (as any number mod 1 is 0) - -3. Initialization: - - `result` is set to 1 (neutral element for multiplication) - - `base` is reduced modulo `modulus` to ensure it's smaller than `modulus` - -4. Main loop: - - Continues while `exp` is greater than 0 - - Uses the binary exponentiation algorithm: - - If the least significant bit of `exp` is 1, multiply `result` by `base` - - Divide `exp` by 2 (right shift by 1 bit) - - Square `base` - - All multiplications are done using modular arithmetic (via `mod_mul` function) - -5. Return the final `result` +1. The function takes three parameters: + - `base`: The base number + - `exp`: The exponent + - `modulus`: The modulus for the operation + All parameters are references to `BigUint` (big unsigned integer) values. + +2. Initialize `result` with 1 (BigUint::one()). + +3. Clone `base` and `exp` to create mutable copies. + +4. Enter a loop that continues while `exp` is greater than zero: + + a. Check if the least significant bit of `exp` is 1 using `exp.bit(0)`. + If true, multiply `result` by `base` and take the modulus. + + b. Square `base` and take the modulus. + + c. Right-shift `exp` by 1 bit (equivalent to dividing by 2). + +5. Return the final `result`. This algorithm is efficient because it reduces the number of multiplications needed from O(exp) to O(log(exp)). diff --git a/content/Shamir's secret sharing.md b/content/Shamir's secret sharing.md new file mode 100644 index 0000000..b244176 --- /dev/null +++ b/content/Shamir's secret sharing.md @@ -0,0 +1,268 @@ +--- +id: 1725904360-shamirs-secret-sharing +aliases: + - Shamir's secret sharing +tags: [] +--- + +# Shamir's secret sharing +## Implementation Steps +1. Implement polynomial evaluation +2. Create a function to generate random coefficients +3. Implement the share generation function +4. Implement the secret reconstruction function + +## Generate Shares +```rust +/// Generates Shamir's Secret Sharing scheme shares for a given secret. +/// +/// # Arguments +/// * `secret` - The secret to be shared +/// * `num_shares` - The total number of shares to generate +/// * `threshold` - The minimum number of shares required to reconstruct the secret +/// +/// # Returns +/// A vector of tuples, where each tuple contains a share ID and its corresponding value +fn generate_shares( + secret: &BigUint, + num_shares: usize, + threshold: usize, +) -> Vec<(BigUint, BigUint)> { + let prime = BigUint::parse_bytes(PRIME.as_bytes(), 10).unwrap(); + let mut rng = thread_rng(); + let mut coefficients = vec![secret.clone()]; + for _ in 1..threshold { + coefficients.push(rng.gen_biguint_below(&prime)); + } + + (1..=num_shares) + .map(|x| { + let x_biguint = BigUint::from(x); + let mut y = BigUint::zero(); + for (i, coeff) in coefficients.iter().enumerate() { + y += coeff * mod_pow(&x_biguint, &BigUint::from(i), &prime); + y %= ′ + } + (x_biguint, y) + }) + .collect() +} +``` + +### Notes +1. Function signature: + ```rust + fn generate_shares( + secret: &BigUint, + num_shares: usize, + threshold: usize, + ) -> Vec<(BigUint, BigUint)> + ``` + This function takes a secret (as a `BigUint` reference), the number of shares to generate, and the threshold (minimum number of shares needed to reconstruct the secret). It returns a vector of tuples, where each tuple represents a share (x, y). + +2. Prime number initialization: + ```rust + let prime = BigUint::parse_bytes(PRIME.as_bytes(), 10).unwrap(); + ``` + This line parses a predefined prime number (PRIME) from a string to a `BigUint`. + +3. Random number generator initialization: + ```rust + let mut rng = thread_rng(); + ``` + This creates a thread-local random number generator. + +4. Coefficient generation: + ```rust + let mut coefficients = vec![secret.clone()]; + for _ in 1..threshold { + coefficients.push(rng.gen_biguint_below(&prime)); + } + ``` + This creates a vector of coefficients for a polynomial. The first coefficient is the secret itself, and the rest are randomly generated numbers below the prime. + +5. Share generation: + ```rust + (1..=num_shares) + .map(|x| { + // ... (explained below) + }) + .collect() + ``` + This generates `num_shares` shares using a range from 1 to `num_shares` (inclusive) and mapping each value to a share. + +6. Share calculation: + ```rust + let x_biguint = BigUint::from(x); + let mut y = BigUint::zero(); + for (i, coeff) in coefficients.iter().enumerate() { + y += coeff * mod_pow(&x_biguint, &BigUint::from(i), &prime); + y %= ′ + } + (x_biguint, y) + ``` + For each share: + - Convert the x value to a `BigUint`. + - Calculate the y value using the polynomial with the generated coefficients. + - Use modular exponentiation (`mod_pow`) for each term. + - Sum up the terms and take the modulus with the prime number. + - Return the (x, y) pair as a share. + +The secret can be reconstructed only when a minimum number of shares (threshold) are combined. + +## Reconstruct Secret +```rust +/// Reconstructs the secret from a set of Shamir's Secret Sharing scheme shares. +/// +/// # Arguments +/// * `shares` - A slice of tuples, where each tuple contains a share ID and its corresponding value +/// * `threshold` - The number of shares used for reconstruction (must match the threshold used in generation) +/// +/// # Returns +/// The reconstructed secret as a BigUint +fn reconstruct_secret(shares: &[(BigUint, BigUint)], threshold: usize) -> BigUint { + let prime = BigUint::parse_bytes(PRIME.as_bytes(), 10).unwrap(); + let prime_int = prime.to_bigint().unwrap(); + let mut secret = BigInt::zero(); + + for i in 0..threshold { + let (xi, yi) = &shares[i]; + let mut numerator = BigInt::one(); + let mut denominator = BigInt::one(); + + for j in 0..threshold { + if i != j { + let (xj, _) = &shares[j]; + numerator *= xj.to_bigint().unwrap(); + numerator %= &prime_int; + denominator *= + (xj.to_bigint().unwrap() - xi.to_bigint().unwrap() + &prime_int) % &prime_int; + denominator %= &prime_int; + } + } + + let term = yi.to_bigint().unwrap() * numerator * mod_inverse(&denominator, &prime_int); + secret += term; + secret %= &prime_int; + } + + if secret < BigInt::zero() { + secret += prime_int; + } + secret.to_biguint().unwrap() +} +``` + +### Notes +1. Function signature: + ```rust + fn reconstruct_secret(shares: &[(BigUint, BigUint)], threshold: usize) -> BigUint + ``` + The function takes a slice of tuples (shares) and a threshold, and returns a BigUint. + +2. Initialize variables: + - `prime`: A large prime number (constant) parsed from a string. + - `prime_int`: The prime converted to BigInt. + - `secret`: Initialized to zero, will hold the reconstructed secret. + +3. Main loop: + Iterates through the shares up to the threshold: + ```rust + for i in 0..threshold { + ``` + +4. For each share: + - Extract `xi` and `yi` from the current share. + - Initialize `numerator` and `denominator` to 1. + +5. Inner loop: + For each share (except the current one): + ```rust + for j in 0..threshold { + if i != j { + ``` + - Update `numerator` and `denominator` using [[Lagrange interpolation formula]]. + - Use modular arithmetic to keep values within the prime field. + +6. Calculate term: + ```rust + let term = yi.to_bigint().unwrap() * numerator * mod_inverse(&denominator, &prime_int); + ``` + - Multiply `yi` by the calculated numerator and the modular inverse of the denominator. + +7. Update secret: + ```rust + secret += term; + secret %= &prime_int; + ``` + - Add the term to the secret and apply modulo operation. + +8. Final adjustment: + ```rust + if secret < BigInt::zero() { + secret += prime_int; + } + ``` + - Ensure the secret is non-negative. + +9. Return: + ```rust + secret.to_biguint().unwrap() + ``` + - Convert the secret back to BigUint and return it. + +This implementation uses the [[Lagrange interpolation formula]] to reconstruct the secret from the given shares. It works in a [[finite field]] defined by the prime number to ensure security and proper reconstruction. + +## Main Function +```rust +/// The main function that demonstrates the usage of Shamir's Secret Sharing scheme. +/// +/// This function: +/// 1. Creates a secret +/// 2. Generates shares for the secret +/// 3. Reconstructs the secret from a subset of shares +/// 4. Compares the reconstructed secret with the original +fn main() { + let secret = BigUint::parse_bytes(b"123456789012345678901234567890", 10).unwrap(); + let num_shares = 5; + let threshold = 3; + + let shares = generate_shares(&secret, num_shares, threshold); + println!("Shares:"); + for (i, share) in shares.iter().enumerate() { + println!("Share {}: ({}, {})", i + 1, share.0, share.1); + } + + let reconstructed_secret = reconstruct_secret(&shares[0..threshold], threshold); + println!("Reconstructed secret: {}", reconstructed_secret); + println!("Original secret: {}", secret); + println!( + "Reconstruction successful: {}", + reconstructed_secret == secret + ); +} +``` + +### Notes +In the above code, `num_shares` and `threshold` are chosen as fixed values: + +```rust +let num_shares = 5; +let threshold = 3; +``` + +These values are hard-coded in the `main` function. What they represent and how they are typically chosen in Shamir's Secret Sharing scheme: + +1. `num_shares` (5 in this case): + - Total number of shares that will be generated from the secret. + - It determines how many participants can hold a part of the secret. + +2. `threshold` (3 in this case): + - Minimum number of shares required to reconstruct the secret. + - It must be less than or equal to `num_shares`. + +The choice of these values depends on the specific security requirements and use case. Some considerations for choosing these values: + +1. Security: A higher threshold increases security, as more shares are needed to reconstruct the secret. +2. Availability: A lower threshold makes it easier to reconstruct the secret if some shares are lost. +3. Number of participants: `num_shares` should be at least equal to the number of participants who need to hold a share. diff --git a/content/Week 2.md b/content/Week 2.md index b63b330..e45d10c 100644 --- a/content/Week 2.md +++ b/content/Week 2.md @@ -14,7 +14,7 @@ tags: [] - [[Fast Fourier Transform]] # Exercises -- Implement basic Shamir's secret sharing +- Implement basic [[Shamir's secret sharing]] # References - https://berthub.eu/articles/posts/reed-solomon-for-programmers/ diff --git a/content/automorphism.md b/content/automorphism.md index 05f69d7..55adc31 100644 --- a/content/automorphism.md +++ b/content/automorphism.md @@ -28,7 +28,7 @@ Fundamental concept in algebra, crucial in [[1725899101-galois-theory|Galois The - Preserve both addition and multiplication - Ex: complex conjugation is an automorphism of $C$ - Field Automorphisms - - Preserve addition, multiplication, and multiplicative inverses + - Preserve addition, multiplication, and [[1725555801-modular-multiplicative-inverse|multiplicative inverse]] - **Crucial in Galois theory** - Vector Space Automorphisms - Linear transformations that are bijective diff --git a/content/index.md b/content/index.md index f69d8b3..d303d0d 100644 --- a/content/index.md +++ b/content/index.md @@ -45,7 +45,7 @@ If you'd like to follow along, you may navigate to [this Notion page](https://ww - [[Fast Fourier Transform]] #### EXERCISES -- Implement basic Shamir's secret sharing +- Implement basic [[Shamir's secret sharing]] #### REFERENCES - https://berthub.eu/articles/posts/reed-solomon-for-programmers/ diff --git a/content/isomorphism.md b/content/isomorphism.md index 04cff69..a5f7188 100644 --- a/content/isomorphism.md +++ b/content/isomorphism.md @@ -27,7 +27,7 @@ Fundamental concept in abstract algebra. Allow us to identify when two seemingly - For rings $R$ and $S$, a bijective map $\varphi$: $R \rightarrow S$ such that: $\varphi(a+b)=\varphi(a)+\varphi(b)$ and $\varphi(ab)=\varphi(a)\varphi(b)$ for all $a,b$ in $R$ - Field Isomorphisms - Similar to ring isomorphisms, but between [[fields]] - - Also preserves multiplicative inverse + - Also preserves [[1725555801-modular-multiplicative-inverse|multiplicative inverse]] - Vector Space Isomorphisms - Linear bijections between vector spaces - Preserve vector addition and scalar multiplication diff --git a/content/modular multiplicative inverse.md b/content/modular multiplicative inverse.md index cfca6c3..b469238 100644 --- a/content/modular multiplicative inverse.md +++ b/content/modular multiplicative inverse.md @@ -24,7 +24,7 @@ tags: [] 4. Calculation: The most common way to calculate the modular multiplicative inverse is using the [[extended Euclidean algorithm]]. -## Rust Implementation +## Rust Implementation 1 ```rust fn mod_inverse(a: &BigUint, m: &BigUint) -> Option { let a = a.to_bigint().unwrap(); @@ -64,3 +64,94 @@ This Rust function implements the [[Extended Euclidean Algorithm]] to find the m - Otherwise, it adjusts `t` to be positive and returns `t % m` as the inverse This algorithm is used in various cryptographic applications, including [[RSA|RSA encryption]]. + +## Rust Implementation 2 +```rust +/// Computes the modular multiplicative inverse of 'a' modulo 'm' using the extended Euclidean algorithm. +/// +/// # Arguments +/// * `a` - The number to find the inverse for +/// * `m` - The modulus +/// +/// # Returns +/// The modular multiplicative inverse of 'a' modulo 'm' as a BigInt +/// +/// # Panics +/// Panics if 'a' is not invertible modulo 'm' +fn mod_inverse(a: &BigInt, m: &BigInt) -> BigInt { + let mut t = BigInt::zero(); + let mut new_t = BigInt::one(); + let mut r = m.clone(); + let mut new_r = a.clone(); + + while new_r != BigInt::zero() { + let quotient = &r / &new_r; + t -= "ient * &new_t; + std::mem::swap(&mut t, &mut new_t); + r -= "ient * &new_r; + std::mem::swap(&mut r, &mut new_r); + } + + if r > BigInt::one() { + panic!("a is not invertible"); + } + if t < BigInt::zero() { + t += m; + } + t +} +``` + +### Notes +1. Function signature: + ```rust + fn mod_inverse(a: &BigInt, m: &BigInt) -> BigInt + ``` + This function takes two references to `BigInt` values (`a` and `m`) and returns a `BigInt`. + +2. Initialization: + ```rust + let mut t = BigInt::zero(); + let mut new_t = BigInt::one(); + let mut r = m.clone(); + let mut new_r = a.clone(); + ``` + - `t` and `new_t` are used to keep track of coefficients in the extended Euclidean algorithm. + - `r` and `new_r` are used to store remainders. + +3. Main loop: + ```rust + while new_r != BigInt::zero() { + let quotient = &r / &new_r; + t -= "ient * &new_t; + std::mem::swap(&mut t, &mut new_t); + r -= "ient * &new_r; + std::mem::swap(&mut r, &mut new_r); + } + ``` + This loop implements the extended Euclidean algorithm: + - Calculate the quotient of `r` divided by `new_r`. + - Update `t` and `r` using the quotient. + - Swap `t` with `new_t` and `r` with `new_r` to prepare for the next iteration. + +4. Check for invertibility: + ```rust + if r > BigInt::one() { + panic!("a is not invertible"); + } + ``` + If the final remainder is greater than 1, it means `a` is not invertible modulo `m`. + +5. Ensure positive result: + ```rust + if t < BigInt::zero() { + t += m; + } + ``` + If `t` is negative, add `m` to make it positive while maintaining the same result modulo `m`. + +6. Return the result: + ```rust + t + ``` + The final value of `t` is the modular inverse of `a` modulo `m`.