/* Questo programma fa parte di una libreria messa a disposizione dei lettori del libro Alessandro Languasco & Alessandro Zaccagnini, Introduzione alla Crittografia, Ulrico Hoepli Editore, Milano, 2004. Algoritmo di Gauss per la determinazione di un generatore del gruppo Z_p^*, dove "p" è un numero primo. Si veda il Paragrafo 6.7 del libro citato. */ #include #include #include #include #include "aritmetica.cc" /* Modificare il valore di questa costante in relazione alla quantità di memoria disponibile */ static const long long MAX_PRIME = 1000000; /* Determina l'ordine di "a" modulo "p", memorizzando le potenze successive di "a" nel vettore "potenze_di_a". Alla fine si ordina il vettore "potenze_di_a" per semplificare la ricerca di un elemento di Z_p^* che non sia una potenza di "a" (vedi sotto). La funzione ritorna l'ordine di "a" in Z_p^*. */ long long calcola_potenze_mod_p(long long p, long long a, std::vector &potenze_di_a) { long long potenza = 1; long long r_a = 0; do { // Appendi la nuova potenza di "a" in fondo al vettore // "potenze_di_a" potenze_di_a.push_back(potenza); potenza = moltiplicazione_mod_n(potenza, a, p); ++r_a; } while (potenza != 1); // Ordina le potenze di "a", ridotte modulo "p", contenute nel // vettore "potenze_di_a". NB. È meglio usare la funzione "sort" // del linguaggio perché è sicuramente piú efficiente sort(potenze_di_a.begin(), potenze_di_a.end()); return(r_a); } /* Decomponi "v = MCM(r_a, r_b) = r_a * r_b / mcd(r_a, r_b)" come "v = n * m", dove 1. n divide r_a; 2. m divide r_b; 3. mcd(n, m) = 1. Strategia: sia d = mcd(r_a, r_b), e sia p un fattore primo di d. Se p^a || r_a, e p^b || r_b: se a >= b assegna il fattore p^a ad n; se a < b assegna il fattore p^b ad m. Gli altri fattori primi di "v" dividono uno solo fra "r_a" ed "r_b" e vengono assegnati ad "n" o "m" rispettivamente. */ long long decomponi(long long r_a, long long r_b, long long &n, long long &m) { long long d = mcd(r_a, r_b); // "v = mcm(r_a, r_b)" è il valore ritornato dalla funzione long long v = r_a * r_b / d; n = 1; m = 1; // Molto inefficiente: sostituire con qualcosa di meglio for(long long p = 2; p <= d; ++p) { if ((d % p) == 0) { // "p" è un fattore primo di "d": // determina la potenza "m_a" con cui "p" divide "r_a" long long m_a = 1; while ((r_a % p) == 0) { r_a /= p; m_a *= p; } // Determina la potenza "m_b" con cui "p" divide "r_b" long long m_b = 1; while ((r_b % p) == 0) { r_b /= p; m_b *= p; } // Elimina il primo "p" dal numero "d" while ((d % p) == 0) d /= p; // Aggiorna il valore di "n" o di "m" con la potenza corretta // del primo "p" if (m_a >= m_b) n *= m_a; else m *= m_b; } } // A questo punto "mcd(r_a, r_b) = 1": // assegna i fattori primi residui ad "n" o "m" // NB: non è necessario scomporre "r_a", "r_b" in fattori n *= r_a; m *= r_b; return(v); } /* Algoritmo di Gauss per la determinazione di un generatore mod "p" N. B.: Questo algoritmo NON determina necessariamente il minimo intero nell'intervallo [2, p-1] che genera Z_p^* */ long long gauss(long long p) { // Se "p = 2", allora 1 è un generatore if (p == 2) return(1); // Da qui in poi, "p" è un numero primo dispari // Cominciamo sempre dal valore "a = 2" perché conviene lavorare // con interi piccoli: qui sfruttiamo il fatto che "p" è dispari long long a = 2; long long v = 1; // Ripetiamo il ciclo fino a quando non si trova un intero di ordine // "p - 1". // NB: se "p" non è un numero primo, questo ciclo viene ripetuto // infinite volte perché nessun elemento di Z_p^* ha ordine "p - 1". while (v < p-1) { std::vector potenze_di_a; // "r_a" è l'ordine di "a" in Z_p^* long long r_a = calcola_potenze_mod_p(p, a, potenze_di_a); // Se "a" è un generatore abbiamo finito if (r_a == p-1) return(a); long long b = 0; // Cerca il primo intero "b > 1" che non sia una potenza di "a". // Il vettore "potenze_di_a[]" è stato riempito con le potenze // successive di "a", ridotte modulo "p", e poi ordinato, nella // funzione "calcola_potenze_mod_p()". while (potenze_di_a[b] == b + 1) ++b; ++b; // A questo punto "b" è un elemento di Z_p^* che non è potenza // di "a": in effetti, è il piú piccolo intero nell'intervallo // [1, p-1] con questa proprietà. // Stiamo sfruttando il fatto che il vettore "potenze_di_a" è // ordinato. std::vector potenze_di_b; // "r_b" è l'ordine di "b" in Z_p^* long long r_b = calcola_potenze_mod_p(p, b, potenze_di_b); // Se "b" è un generatore abbiamo finito if (r_b == p-1) return(b); // Se si raggiunge questo punto, né "a" né "b" sono generatori. // Da qui in poi si determina un altro elemento di Z_p^* con // ordine piú grande sia di "a" che di "b". long long n, m; v = decomponi(r_a, r_b, n, m); long long e_a = r_a / n; long long e_b = r_b / m; long long nuovo_a = potenze_mod_n(a, e_a, p); long long temp = potenze_mod_n(b, e_b, p); nuovo_a = moltiplicazione_mod_n(nuovo_a, temp, p); // "nuovo_a" ha ordine "v > max(r_a, r_b)" a = nuovo_a; // Se il nuovo valore di "a" è un generatore abbiamo finito if (v == p-1) return(a); // In caso contrario, continuiamo con il ciclo while, con un nuovo // valore di "a", avente un ordine piú grande dell'ordine del // valore precedente di "a". } // Questa istruzione dovrebbe essere irraggiungibile, ma è // richiesta dal compilatore. return(-1); } int main() { long long unsigned primes[] = {3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, \ 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, \ 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, \ 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, \ 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, \ 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, \ 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, \ 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, \ 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, \ 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, \ 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, \ 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, \ 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, \ 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, \ 953, 967, 971, 977, 983, 991, 997 }; // Come prova, calcoliamo i generatori modulo i primi fino a 1000 long long p; for (unsigned i = 0; i < sizeof(primes)/sizeof(p); ++i) { p = primes[i]; std::cout << "Gauss (" << p << "): ---> " << gauss(p) << std::endl; } // Calcoliamo generatori modulo altri primi p = 65537; std::cout << "Gauss (" << p << "): ---> " << gauss(p) << std::endl; p = 65543; std::cout << "Gauss (" << p << "): ---> " << gauss(p) << std::endl; p = 999983; std::cout << "Gauss (" << p << "): ---> " << gauss(p) << std::endl; }