/* 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 Shanks per la determinazione del logaritmo discreto nel gruppo Z_p^*, p primo. Si veda il Paragrafo 6.8.1 del libro citato. */ #include #include #include #include #include "aritmetica.cc" /* Questo dato contiene una coppia del tipo "(g^a mod p, a)", dove "g" è un generatore di Z_p^*, "a" è un intero nell'intervallo [0, p-1]. */ typedef struct coppia_potenza_esponente { long long potenza; long long esponente; }; /* Modificare il valore di questa costante in relazione alla quantità di memoria disponibile */ static const long long unsigned MAX_PRIME = 1000000; /* Questa funzione stabilisce quale sia "minore" fra due oggetti di tipo "coppia_potenza_esponente": è rilevante solo il valore nel campo "potenza". Questa funzione, passata come argomento all'algoritmo di "sort", permette un ordinamento efficiente delle coppie generate nei "Baby steps". In sostanza, le coppie "(a^n mod p, n)", sono ordinate lessicograficamente, tenendo conto solo del primo elemento della coppia. */ bool minore(coppia_potenza_esponente a, coppia_potenza_esponente b) { if (a.potenza < b.potenza) return(true); else return(false); } /* Ricerca binaria del valore "x" nel vettore ordinato "coppie" */ long long ricerca_binaria(long long x, std::vector &coppie) { long long unsigned dim = coppie.size(); long long unsigned inizio = 0; long long unsigned fine = dim - 1; // NB: se "inizio = fine - 1" si entra in un ciclo infinito a causa // degli arrotondamenti while (inizio < fine - 1) { long long unsigned meta = (inizio + fine) / 2; // Se si trova l'elemento desiderato, si ritorna la sua posizione if (coppie[meta].potenza == x) return(meta); // Se non si trova l'elemento cercato, si elimina la metà del // vettore che certamente non lo contiene if (coppie[meta].potenza < x) inizio = meta; else fine = meta; } // Se si esce dal ciclo while, allora "inizio = fine", oppure // "inizio = fine - 1" if (coppie[inizio].potenza == x) return(inizio); if (coppie[fine].potenza == x) return(fine); // Se l'elemento non compare, restituisci -1 return(-1); } /* Calcolo del logaritmo discreto modulo "p" per mezzo dell'Algoritmo di Shanks. Qui 1. "p" è un numero primo; 2. "g" è un generatore di Z_p^*; 3. "x" è l'elemento di Z_p^* di cui si cerca il logaritmo discreto. La funzione ritorna "y" in Z_p^* tale che g^y = x (in Z_p^*). NB: La funzione NON controlla che "p" sia primo né che "g" sia un generatore. */ long long shanks(long long p, long long g, long long x) { if (p < 0) p *= -1; // "m" è il numero di Baby steps necessari long long unsigned m = 1 + radice_quadrata(p); if (m >= MAX_PRIME) return(-1); // Memorizziamo le coppie "(g^i mod p, i)", per i = 0, ..., m-1 // nel vettore valori std::vector valori; long long potenze_di_g = 1; // Baby steps: calcolo delle potenze successive del generatore "g" for(unsigned i = 0; i < m; ++i) { coppia_potenza_esponente valore; valore.potenza = potenze_di_g; valore.esponente = i; valori.push_back(valore); if(potenze_di_g == x) return(i); potenze_di_g = moltiplicazione_mod_n(potenze_di_g, g, p); } // A questo punto "potenze_di_g = g^m mod p" if (x == potenze_di_g) return(m); std::cout << "Baby steps, prima dell'ordinamento "; for(unsigned i = 0; i < m; ++i) std::cout << valori[i].potenza << " (" << valori[i].esponente << ") "; std::cout << std::endl; // Invochiamo l'algoritmo sort sul vettore "valori", usando la // funzione di confronto "minore" definita sopra sort(valori.begin(), valori.end(), minore); std::cout << "Baby steps, dopo l'ordinamento "; for(unsigned i = 0; i < m; ++i) std::cout << valori[i].potenza << " (" << valori[i].esponente << ") "; std::cout << std::endl; // Preparazione dei Giant steps long long lambda; long long mi; long long MCD = mcd_est(potenze_di_g, p, lambda, mi); std::cout << "mcd come combinazione lineare "; std::cout << MCD << " = " << potenze_di_g << " * " << lambda << " + "; std::cout << p << " * " << mi << std::endl; // Correggi "lambda" se "lambda < 0", in modo che appartenga // all'intervallo [0, p-1] if (lambda < 0) lambda += p; // Ora "lambda" è l'inverso di "potenze_di_g mod p" // Giant steps long long potenze_alte = moltiplicazione_mod_n(lambda, x, p); for(unsigned i = 0; i < m; ++i) { std::cout << "x * g^" << -(i+1) * m << " = " << potenze_alte << std::endl; // Ricerca binaria long long j = ricerca_binaria(potenze_alte, valori); if (j != -1) return((i+1) * m + valori[j].esponente); potenze_alte = moltiplicazione_mod_n(potenze_alte, lambda, p); } // Ritorna -1 in caso di errore; per esempio, se "g" non fosse un // generatore, il ciclo qui sopra potrebbe non dare una risposta return(-1); } void SHANKS(long long p, long long g, long long x) { long long log_discreto = shanks(p, g, x); std::cout << "Il logaritmo discreto di " << x; std::cout << " rispetto al generatore " << g; std::cout << " in Z/" << p << "^* e' " << log_discreto << std::endl; std::cout << "---------------------------------------"; std::cout << "---------------------------------------" << std::endl; } int main() { long long p; long long g; long long x; // Alcuni esempi di uso della funzione "SHANKS" p = 101; g = 3; x = 30; SHANKS(p, g, x); p = 9973; g = 2; x = 5; SHANKS(p, g, x); p = 65537; g = 3; x = 5; SHANKS(p, g, x); p = 61; g = 2; x = 15; SHANKS(p, g, x); p = 61; g = 2; x = 24; SHANKS(p, g, x); p = 43; g = 3; x = 5; SHANKS(p, g, x); p = 31; g = 3; x = 7; SHANKS(p, g, x); x = 1; for(int i = 1; i < 31; ++i) { SHANKS(p, g, x); x *= g; x = x % p; } // 2 non è un generatore p = 31; g = 2; x = 6; SHANKS(p, g, x); }