Uma análise comparativa entre os testes de primalidade AKS e Miller-Rabin Fernando de Farias Universidade Católica de Brasília Curso de Matemática Orientador: José Eduardo Castilho RESUMO Os algoritmos para determinação de primalidade são classificados em duas classes de métodos: os determinísticos e os probabilísticos. Em geral, os algoritmos probabilísticos são de tempo polinomial enquanto que os determinísticos são de tempo exponencial. O AKS é o primeiro algoritmo determinístico a executar este teste em tempo polinomial. Este trabalho faz uma análise entre o método AKS e o método Miller-Rabin, que é um dos métodos probabilísticos mais utilizados. Para isto, foi feita implementação na linguagem do Maple para a análise da comparação do desempenho de cada algoritmo. Palavras-chave: primalidade, números primos. 1. INTRODUÇÃO Comprovar a primalidade de um número natural significa comprovar que este número só admite como divisores positivos ele próprio e o número um. Os números que não são primos são conhecidos como números compostos. Todo número composto pode ser escrito de forma única (a menos da ordem dos fatores) como o produto finito de números primos. Com isto pode-se dizer que os números primos formam uma base para a construção dos números inteiros. Este fato justifica o porquê dos números primos serem objeto de estudo desde a antigüidade até os tempos atuais. Testar a primalidade com números pequenos, até que a tarefa não é das mais complicadas, mas à medida que os números se tornam maiores, a comprovação da primalidade tornar-se muito problemática. Isto se deve ao fato de não se conhecer um procedimento de geração de números primos. “A importância dos testes de primalidade se deve principalmente aos sistemas de criptografia, onde existe uma necessidade de gerar números primos com ordem de grandeza acima de 100 dígitos, que é vital para a segurança dos criptosistemas. Os primeiros algoritmos criados para testar a primalidade de um número remontam à Grécia antiga. Até 2002, os principais algoritmos desenvolvidos eram enquadrados em duas grandes classes”. (BRAGA, 2002): • • De tempo não-polinomial e determinísticos: Afirmam com 100% de certeza a primalidade de um número, mas o cálculo é realizado em tempo exponencial. Exemplos: Crivo de Eratóstenes e Adleman- Rumely, que apresentam tempo de O (log log log n ) execução da ordem de log(n ) . De tempo polinomial, mas não-determinísticos: A complexidade do algoritmo é em função de um polinômio. O tempo de cálculo não 'explode' quando o número testado é muito grande, mas não dão certeza absoluta quanto à primalidade. Exemplos: Teste de MILLER-RABIN. 1 Entretanto no final de 2002, um professor indiano, Manindra Agrawal, e seus dois alunos, Neeraj Kayal e Nitin Saxena, descobriram um algoritmo que está sendo chamado de AKS (iniciais de seus nomes), que permite verificar, sem margem de erro, se um inteiro positivo é ou não primo, em tempo polinomial. Neste trabalho se faz uma análise dos testes probabilísticos e os determinísticos, onde são abordados dois testes para análise comparativa: o Testes de Miller-Rabin, que é um teste probabilístico pertencente a família Teste de Monte Carlo, com margem de erro muito pequena, da ordem de 10 −24 , e o Teste do AKS, que é determinístico e de tempo polinomial. Os algoritmos têm como fundamentação teórica o Pequeno Teorema de Fermat, o qual será apresentado na próxima seção. 2. PEQUENO TEOREMA DE FERMAT Vários algoritmos eficientes de teste de primalidade sejam determinísticos ou probabilísticos se baseiam no Pequeno Teorema de Fermat (PTF): Teorema 2.1 Se p é um primo e se não divide o inteiro a ( p /| a) , então: p é primo ⇒ Demonstração: a p −1 ≡ 1 (mod p ) Considere os p − 1 primeiros múltiplos positivos de a , isto é, os inteiros: a, 2a, 3a, ..., ( p − 1)a Nenhum desses inteiros é congruente a 0 (mod p ) , além disso, dois quaisquer deles são incongruentes (mod p ) , pois, se fossem: ra ≡ sa (mod p ), 1 ≤ r < s ≤ p − 1 Então, o fator comum a poderia ser cancelado, porque o mdc (a, p ) = 1 , e teríamos: r ≡ s (mod p ) o que é impossível, visto que 0 < s − r < p. Assim sendo, cada um dos inteiros a, 2a, 3a, ..., ( p − 1)a é congruente (mod p ) a um único dos inteiros 1, 2, 3, ..., p − 1 , considerados uma certa ordem, e por conseguinte multiplicando ordenadamente todas essas p − 1 congruências por a , teremos: a ⋅ 2a ⋅ 3a ..., ( p − 1)a ≡ 1 ⋅ 2 ⋅ 3 ⋅ ...( p − 1) (mod p ) ou seja: a p −1 ( p − 1) ! ≡ ( p − 1) ! (mod p ) 2 Como p é primo e p não divide ( p − 1) !, podemos cancelar o fator comum ( p − 1) !, o que dá a congruência de Fermat: a p −1 ≡ 1 (mod p ) □ Os números que satisfazem o PTF são chamados de prováveis primos. O termo prováveis se deve ao fato de que o PTF não garante a volta. Para cada valor de a existe uma infinidade de não primos que satisfaz esse teste os quais são chamados de pseudoprimos ou falsos primos. Como exemplo tem-se o número de Carmichael, p = 1001152801 , que satisfaz o PTF, mas é um número composto, pois este pode ser expresso como p = 11× 41× 61× 151× 241 . 3. ALGORITMO AKS O algoritmo AKS ganhou destaque por ser o primeiro algoritmo publicado que é simultaneamente polinomial, determinístico, e incondicional. O que isto significa, o tempo máximo de processamento do algoritmo pode ser expresso como um polinômio em relação ao número de dígitos do número analisado. Isto permite classificar o número informado como primo ou composto ao invés de retornar um resultado probabilístico. O ponto chave do algoritmo tem como base o seguinte Teorema: Teorema 3.1 Suponha que a ∈ Z *p . Então p > 1 é primo se: ( x + a) p ≡ x p + a (mod p ). Demonstração: Pelo Teorema Binomial de Newton, temos que: p p ( x + a ) p = ∑ x p − j a j , j =0 j p Mas nos casos em que j é diferente de 1 e p , o coeficiente binomial é divisível por p , logo j todos os termos intermediários desta expansão são divisíveis por p , ou seja, são concruentes a zero modulo p . Assim ( x + a ) p ≡ x p + a p ≡ x p + a (mod p ). Onde na última congruência é devida ao PTF. Desta forma obtém-se o seguinte critério de primalidade p é primo ⇔ ( x + a ) p ≡ x p + a (mod p ), para todo a < p 3 O conceito básico do algoritmo AKS é a congruência do Teorema 3.1. Analisando a expansão p p binomial inteira de ( x + a ) , ou seja, os temos com j ≠ 1 e p ,pode-se determinar se p é j primo, pois caso todos os termos da expansão com j = 1,... p − 1 forem divisíveis por p , então p seria primo, caso contrário teríamos p composto. Pode-se verificar esta propriedade no Triangulo de Pascal, onde cada linha representa os coeficientes da expansão binomial. Triangulo de Pascal 0: 1 1: 1 1 2: 1 2 1 3: 1 3 3 1 4: 1 4 6 4 1 5: 1 5 10 10 5 1 6: 1 6 15 20 15 6 1 7: 1 7 21 35 35 21 7 1 8: 1 8 28 56 70 56 28 8 1 9: 1 9 36 84 126 126 84 36 9 1 10: 1 10 45 120 210 252 210 120 45 10 1 Para p muito grande este tipo de teste é impraticável e o AKS implementa o teste, descrito a seguir. A idéia do AKS é tornar o teste binomial rápido e ainda ser capaz de provar que o teste responde corretamente mesmo quando p é composto. Ao invés de trabalharmos com módulo p , trabalhamos com módulo um polinômio x r − 1 (onde r é um primo razoavelmente pequeno). Ou p p seja, no lugar de calcular ( x + a ) , calcula-se o resto da divisão de ( x + a ) por x r − 1 , que é feito usando o mesmo método em Álgebra para dividir um polinômio por outro. Dessa maneira, p teremos no máximo r − 1 termos para examinar, enquanto que na expansão de ( x + a ) temos p −1 . Desta forma, o AKS considera a seguinte congruência: (x + a ) p ≡ (x p + a ) (mod( x r − 1, p)) , (3.1) onde (mod( x r − 1, p ) ) representa aplicar (mod x r − 1) e ao resultado aplicar (mod p ) . Como a congruência original é satisfeita sempre que p é primo, então tem-se que a congruência (3.1) também é satisfeita quando p é primo. A justificativa é que quando duas expressões são iguais, 4 apresentam o mesmo resto quando se divide essas duas expressões por x r − 1 . O que não é óbvio é que a nova congruência é sempre falsa quando n é composto, pois mesmo quando (x − a )n ≡/ (x n − a ) (mod n ) é possível que dois diferentes polinômios tenham o mesmo resto quando divididos por x r − 1 O que o AKS mostra é que se p é composto, e se for escolhido o valor de r , que satisfaz as condições (3.2) q > 2 r log p e p (r −1) / q ≡/ 1(mod r ) , onde q é o maior fator primo de r − 1 . Desta forma basta testar apenas um número pequeno de a até encontrarmos um tal que (x − a ) p ≡/ x p − a mod x r − 1, p . □ ( ) ( ) Uma vez que se encontra um valor de a que satisfaz a relação acima, prova-se que p é composto. O valor de a deve estar no intervalo entre 1 e 2 r log p , (BRAGA, 2002). Algoritmo AKS 1. Entrada p > 1 2. Se p = a b com b > 1 , retorna COMPOSTO 3. r ← 2 4. Enquanto (r < p ) { 5. Se mdc (r , p ) ≠ 1 , retorna COMPOSTO 6. Se r é primo{ 7. Encontre q o maior divisor primo de r − 1 Se q > 2 r log p e p (r −1) / q ≡/ 1(mod r ) vai para 10.} 8. 9. r ← r + 1 } 10. Para a = 1 até 2 r log p faça 11. Se (x + a ) ≡/ x p + a (mod x r − 1, p ) retoma COMPOSTO 12. retorna PRIMO p Os passos do algoritmo, numerados de um a nove, usam alguns conceitos algébricos que descartam valores que não influenciam na determinação da primalidade. No passo dois verificase, se n é uma potência de um número inteiro. Isto é feito por um algoritmo em tempo polinomial. Nos passos de três a nove procura-se os valores de r e q que satisfaçam as condições (3.2). Nos passos dez e onze verifica-se a existência de um valor de a que não satisfaça a condição (3.1), retornando que o número é composto. Caso contrário tem-se que o número p é primo. 5 4. TESTE DE MILLER-RABIN “Com teste de Miller-Rabin inaugurou a classe de testes probabilísticos chamada de Testes de Monte Carlo. Estes testes são usados largamente na maioria das funções de teste de primalidade presentes nos softwares matemáticos, dada a sua velocidade e o grau de confiabilidade. De fato, o esse algoritmo não verifica a primalidade de um número, a única coisa que ele detecta com segurança é se o número é composto”. (COUTINHO, 2003). Ele faz um teste, que verifica se o número é composto. “Caso o teste seja falho existe a probabilidade de 75% do número ser primo. Aplica-se novamente o teste e se este for falho a probabilidade do número ser primo passa a ser 93%”. (MARTINEZ e CAMPOS, 2004). O procedimento pode ser repetido e na quinta iteração tem-se uma probabilidade aproximada de 99.9% do número ser primo. Se o número passar no teste, ele é composto. É importante dizer que o teste Miller-Rabin, como às vezes também é chamado, não dá indícios sobre a fatoração prima do número n. O algoritmo tem como base o resultado do seguinte teorema: Teorema 4.1 Seja p ímpar e p − 1 = 2 k q com q ímpar e k ≥ 1 . Se p é primo e a ∈ Z *p , então a q ≡ 1(mod n ) ou existe um i ∈ {0,1,..., k − 1} tal que a 2 q ≡ 1(mod p ) i Demonstração: Consideremos a seguinte seqüência de potencias módulo p : a q , a 2 q ,..., a 2 k −1 q , a2 k q Se p for um número primo, então pelo menos uma destas potências tem que ser congruente a 1 modulo p , pois, pelo (Teorema 2.1), temos a2 k q = a p −1 ≡ 1(mod p ) . 2 q Seja i ≥ 1 o menor expoente tal que a ≡ 1(mod p ) . Podemos escrever i ( ) ( + 1). − 1) ou p | (a + 1). Sendo i o menor Como p é primo e p | (a − 1) , então ou p | (a − 1) não é divisível por p . Segue que expoente tal que (a − 1) é divisível por p , então (a + 1), isto é, a − 1 ≡ −1 (mod p ) . p divide (a a2 q − 1 = a2 i i −1 − 1 ⋅ a2 2i −1 q 2i q 2i q 6 i −1 q 2i −1 q 2i −1 q 2i q 2i −1 q q Concluído que se p é primo, então uma das potências da seqüência dada tem que ser congruente a − 1(mod p ) quando i ≥ 1 . Agora, se i = 0 então a q ≡ 1(mod p ) e a esta congruência não podemos aplicar o produto notável, pois q é ímpar. Portanto, se p é primo, então uma das potências da seqüência é congruente a − 1 módulo p ou □ a q ≡ 1(mod p ) Se os primos se destinarem para o uso "industrial" (por exemplo, para a encriptação RSA), geralmente não é necessário provar sua primalidade. É suficiente saber que a probabilidade do número ser composto é menor do que 10 24 %. Neste caso, podem-se utilizar os testes (fortes) de primalidade provável. Algoritmo Miller-Rabin 1. escolha a ∈ {1,..., p − 1} aleatório 2. escreva p − 1 = 2 t q, q ímpar 3. calcule sucessivamente a0 = a q (mod p ), a1 = a02 (mod p ),...a k = a k2−1 (mod p ) até que k = t ou a k ≡ 1(mod p ) 4. se k = t e a k ≡/ 1(mod p ) retorne COMPOSTO 5. senão k = 0 então retorne PRIMO 6. senão a k −1 ≡/ −1(mod p ) então retorne COMPOSTO 7. senão retorne PRIMO 5. ANÁLISE COMPARATIVA Nesta seção faz-se um teste comparativo entre os algoritmos AKS e Miller-Rabin. O objetivo é comparar a eficiência no contexto computacional. Para isto, implementou-se os algoritmos, usando o Maple com o pacote numtheory . Os códigos e detalhes da implementação, são apresentados em anexo. Os testes foram realizados num computador: Pentium 4 CPU 3.00 GHz, 512 MB de RAM. TABELA 1: Analise comparativa Tempo Números 1001152801 1000151 4339 155121545123548965423579 514269624785214512122156 706197 AKS 0.734 s 4700 s “ ran out of memory” 3.891 s RABIN 0s 0.47 s 0.781 s 0.31 s 7 0.078 O primeiro número testado foi um pseudo primo, o AKS levou 0.734 para testar o número, o RABIN com a probabilidade mais de 99,9% levou 0 segundos fazer a observação de que este zero significa tempo menor que 0.001 comparando com o tempo do AKS, ambos verificam que o número não é primo. Segundo é um primos de 7 dígitos, AKS levou 4700 s em média para retornar que não havia memória suficiente para verificar sua primalidade enquanto o RABIN levou 0.47 segundos com a probabilidade de 99,9% de esse número ser primo. O terceiro exemplo é um número primo, para o qual o AKS levou 3.891 segundos. Neste caso o valor de r é igual ao valor de p . O quarto número é composto, AKS levou 0.781 segundos para retornar que o número era composto e RABIN levou 0.31 segundos com a probabilidade desse número ser primo de menos 0.001%. O teste de Miller-Rabin, aplicado ao número 45127, dá como verdadeiro que este seja primo. No entanto a implementação do método AKS, apresentada neste trabalho, não conseguiu fazer a verificação após 10 horas de processamento. Isto se deve a forma como o passo 11 do algoritmo foi implementado. A limitação de memória tem ocorrido no passo 11 do algoritmo. Este passo tem sido um limitante na implementação apresentada. Neste aspecto, o Rabin tem sido mais eficiente. Foi testado o primo de 80 dígitos, apresentado abaixo, e o Rabin respondeu como sendo primo, num tempo de 0.52s, enquanto o AKS já está dando limitação de memória para primos com 7 dígitos. Primo de 80 dígitos: 10011528011151515151234511521212158784512154155512121548789555895515121261321153 6. CONCLUSÃO Em todos os casos testados verificou-se a eficiência na questão tempo de processamento e memória no algoritmo do Miller-Rabin é superior ao algoritmo do AKS. “Na implementação do algoritmo AKS o passo 11 calcula duplo módulo entre polinômios de grau p e r . Esta divisão polinomial foi implementada com o uso do comando rem do pacote numtheory do Maple, A implementação eficiente deste passo tem sido um desafio aos programadores”. (SANTOS e ENOQUE). Apesar do número de operações ser de tempo polinomial, este tem sido o passo significativo no cálculo do tempo total de execução. Crandall e Papadopoulos (2003) sugerem uma nova implementação para este passo que deve melhorar a desempenho do algoritmo, mas que não pode ser testada neste trabalho, por exigir uma linguagem de programação de baixo nível. Outro fator que deve melhorar a “performance” do algoritmo é se fazer uma implementação num ambiente de cálculo, que não seja simbólico. O AKS tem sua relevância do ponto de vista matemático, mas em termos práticos o Rabin tem-se mostrado mais eficiente. 8 REFERÊNCIAS BIBLIOGRÁFICAS BRAGA, da R. B, Algoritmo AKS primalidade de um numero em tempo polinomial, UFRJ artigo de conclusão de curso, 11 de setembro, 2002. COUTINHO, S. C. Uma introdução ao algoritmo AKS, Coleção Iniciação cientifica, Sociedade brasileira de matemática. 2003 CRANDALL, R. e PAPADOPOULOS, J. On the implementation of AKS-class primality test, Advanced Computation Group. Apple Computer e University of Maryland College Park. 2003 MARTINEZ, E. B. F e CAMPOS, M. T, Algoritmo AKS para verificação de primalidade em tempo polinomial, 2004, < http://www.cic.unb.br/~pedro/trabs/primal.htm >, Acesso em 28/08/2007. SANTOS, P. NETO, R. X. e ENOQUE T. Uma tentativa de implementação do algoritmo de primalidade AKS. 2002, Disponível em <http://www.cic.unb.br/~pedro/trabs/primal.htm> , Acesso em 10/11/2007. Fernando de Farias Nunes ([email protected]) Curso de Matemática, Universidade Católica de Brasília EPCT – QS 07 – Lote 01 – Águas Claras – Taguatinga – CEP.: 72966-700 9 ANEXOS. A1: Algoritmo AKS. Esta implementação tem como base o código apresentado por Braga (2004). A implementação original faz uso a função isprime, que é um procedimento probabilístico. Isto influencia nos resultados de eficiência do algoritmo. Desta forma optou-se por substituir a função isprime pelo Crivo de Erastóteles. Deve-se observar que nesta fase do algoritmo, o número r , que deve ser testado pelo Crivo, é bem pequeno com relação ao número p . # Procedimento AKS aks := proc (n) local r, fs, q, a; if (NisAB(n)) then RETURN(false) fi; r := 2; while r < n do if igcd(n, r) <> 1 then RETURN(false); fi; # n is composite if crivo(r) then if r = 2 then q := 2; else fs := factorset(r-1); q := fs[-1]; fi; if q >= simplify(4.0*sqrt(r)*log(n)) and (n^((r-1)/q) mod r) <> 1 then break; fi; fi; if r > 2 then r := r + 2; else r:=r+1; end if; # print(`Valor de r = `,r); od; print("Valor de r = ",r); for a from 1 to simplify(2.0*sqrt(r)*log(n)) do if ((rem((x-a)^n, x^r-1, x) mod n)<>(rem(x^n-a, x^r-1, x) mod n)) then RETURN(false); # n is composite fi; od; RETURN(true); # n is prime end: # Função que checa se n = a^b NisAb := proc(n) local b, ret, lim; ret:=false; lim:= trunc(evalf((log(n))/log(2))); for b from 2 to lim while (not ret) do if type(eval(exp(log(n)/b)),integer) then if type(eval(root(n,b)),integer) then ret := true end if; 10 fi; od; return(ret); end: # Crivo de Erastóteles crivo:=proc(n::integer) local k; k:=3; while (k < sqrt(n*1.0)) do if n mod k = 0 then return false; end if; k:=k+2; end do; return true; end: A2: Algoritmo Miller-Rabin. # Teste de Miller-Rabim MRtest := proc (N::integer,t::integer) local a,d,e,j,k,s,u,f,found,randomelement; randomelement := rand(2..N-1); for s from 1 to t do a := randomelement(); if N mod a = 0 then return(false) fi; d := igcd(a,N); if d>1 then return(false) fi; e := a&^(N-1) mod N; if not (e=1) then return(false) fi; # print(`Iniciando o teste de Miller-Rabin`); u := N-1; k := 0; while (u mod 2 = 0) do u := u/2; k := k+1; od; f := a &^u mod N; # print(f); if (f=-1 mod N) or (f=1 mod N) then break fi; 11 found := false; for j from 1 to k do f := f &^2 mod N; # print(f); if f=-1 mod N then found := true; break fi; od; if found then break else return(false) fi; od; print(`Teste MR`,t,`vezes aplicado:`,N,` supostamente é primo`); return(true); end: 12