Algo rho de Pollard en C, avec GMP

Bonjour,

Je code un petit programme qui doit mettre en pratique l’algorithme rho de Pollard. J’utilise pour ce faire la librairir GMP.

Programme principalement codé par Syam, merci à lui
Si vous avez un moyen de l’améliorer, n’hésitez pas !

Voici le code en vrac.

[code]#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>

void f(mpz_t, unsigned int, mpz_t);
void prho(unsigned int, mpz_t);

// Global, utilisé pour les comparaisons avec la constante 1
// On ne peut pas simplement utiliser (mpz_get_ui(…) == 1) car :
// If op is too big to fit an unsigned long then just the least significant bits that do fit are returned.
// The sign of op is ignored, only the absolute value is used.
mpz_t mpz1;

int main(int argc, char** argv)
{
FILE* file;
mpz_t n;
unsigned int i = 1;

if ((argc < 2) || (argc > 3) || !(file = fopen(argv[1], “r”)))
{
fprintf(stderr, “Usage: prho FICHIER [ENTIER]\n”);
exit(1);
}

mpz_init_set_ui(mpz1, 1);
mpz_init(n);
mpz_inp_str(n, file, 10);
fclose(file);
gmp_printf(“N = %Zd\n”, n);

if (argc == 3)
i = atoi(argv[2]);
prho(i, n);

mpz_clear(n);
mpz_clear(mpz1);
return 0;
}

// f(x) = (x^2 + c) mod n, c != 0,-2
void f(mpz_t x, unsigned int i, mpz_t n)
{
mpz_pow_ui(x, x, 2);
mpz_add_ui(x, x, i);
mpz_mod(x, x, n);
}

// rho de Pollard :
//
// Inputs: n, the integer to be factored; and f(x), a pseudo-random function modulo n
//
// Output: a non-trivial factor of n, or failure.
//
// 1. x ← 2, y ← 2; d ← 1
// 2. While d = 1:
// 1. x ← f(x)
// 2. y ← f(f(y))
// 3. d ← GCD(|x − y|, n)
// 3. If d = n, return failure.
// 4. Else, return d.
//
void prho(unsigned int i, mpz_t n)
{
mpz_t x, y, d, tmp;

mpz_init_set_ui(x, 2);
mpz_init_set_ui(y, 2);
mpz_init_set_ui(d, 1);
mpz_init(tmp);

while (mpz_cmp(d, mpz1) == 0)
{
// x ← f(x)
f(x, i, n);
// y ← f(f(y))
f(y, i, n);
f(y, i, n);

// d ← GCD(|x − y|, n)
mpz_sub(tmp, x, y);
mpz_abs(tmp, tmp);
mpz_gcd(d, tmp, n);

}

mpz_clear(x);
mpz_clear(y);
mpz_clear(tmp);

// if there is a factor
if (mpz_cmp(d, n) != 0)
{
// n := n / d
mpz_tdiv_q(n, n, d);

//i + 1 arbitrary chosen evol, 
prho(++i, d);

if (mpz_cmp(n, mpz1) != 0)
  prho(i, n);

}
else
gmp_printf("%Zd\n", n);

mpz_clear(d);
}[/code]

pour compiler (avec libgmp3-dev d’installé)
gcc main.c -o prho -lgmp

il faut appeller le programme ainsi
prho fichiercontenantunentier [unentierautrequezerooudeux]

essayez par exemple avec 8051, 240, 4 … Pour voir les différents résultats. Essayez aussi avec 222119, de cette manière :
echo 222119 > ./n
./prho n
./prho n 5

Encore et toujours imparfait.

C’est relativement explicite : tu essayes de faire un modulo zero.
Pour une raison ou pour une autre, ton n est égal à zéro.

Comme tu appelles f() plusieurs fois de suite, le mpz_clear(n) est-il bien à sa place dans f() ? M’est avis que non, vu que ni f() ni prho() ne sont propriétaires de cette donnée. Rappelle toi la règle d’or : seul son propriétaire a le droit de libérer une ressource !

Edit : en fait c’est encore pire…

  1. main() alloue un n, qui est passé en paramètre à prho()
  2. prho() réinitialise ce n, qui est ensuite passé en paramètre à f()
  3. f() utilise ce n puis l’efface (f() est appelée 3 fois de suite)
  4. récursion 2/3
  5. main() efface n

Je connais pas libgmp précisément, mais de toutes façons y’a un gros souci là, faut revoir ton organisation.

Je suis d’accord pour dire que c’est en bordel… Mais l’occurence, à chaque fois que je créé un mpz_t, il a une nouvelle adresse et n’est accessible qu’en local de mes instructions non ? Donc même si je nomme plusieurs n, ce n’est jamais le même qui est appelé ?

C’est pourquoi d’ailleurs j’utilise le c++, puisque pour f(x), je ne peux pas initialiser et faire un clear sur les valeurs GMP enregistrées : j’agit donc directement sur ce qui m’est donné en argument.

Bon, je nettois tout ça, un peu de rigueur ne peut de toute façon pas faire de mal.

edit : nettoyé : chaque nom de variable est unique. Je passe toujours les adresses des variables GMP, elles sont toutes ainsi initialisée une fois et supprimée avant de quitter le programme.

Mais ça ne change rien : floating point exception.

GDB :

code r n
Starting program: /home/xkth/prog/crypto/pRho/prho n
N=%

Program received signal SIGFPE, Arithmetic exception.
0x00007ffff7b8b99e in __gmp_exception () from /usr/lib/libgmp.so.3
(gdb) bt
#0 0x00007ffff7b8b99e in __gmp_exception () from /usr/lib/libgmp.so.3
#1 0x00007ffff7b8b9be in __gmp_divide_by_zero () from /usr/lib/libgmp.so.3
#2 0x00007ffff7ba73b0 in __gmpz_tdiv_r () from /usr/lib/libgmp.so.3
#3 0x00007ffff7ba059b in __gmpz_mod () from /usr/lib/libgmp.so.3
#4 0x0000000000400da0 in p(__mpz_struct (&) [1], unsigned int, __mpz_struct (&) [1]) ()
#5 0x0000000000400e2e in prho(unsigned int, __mpz_struct (&) [1]) ()
#6 0x0000000000400f60 in prho(unsigned int, __mpz_struct (&) [1]) ()
#7 0x0000000000400d35 in main ()[/code]

Ceci fait, le problème est bien l’initialisation de n donc… même en faisant ainsi :
mpz_init(n);
mpz_set_ui(n,atol(argv[1]));
gmp_printf(“N=%Z\n”,n);

Qui semble être la base pour initialiser une telle variable, n est totalement vide. même pas à 0 donc, ce que devrait faire mpz_init(); .

Il doit y avoir un truc ultra simple qui m’échappe.

Techniquement, lors d’un passage de paramètre par valeur tu copies effectivement la structure (mpz_t). Mais en pratique, cette structure contient… un pointeur vers les données réelles. Donc, même quand tu passes ça par valeur tu ne travailles pas sur une copie.

Je n’ai pas encore regardé en détail la nouvelle version avec passage par référence, je m’y atèle de ce pas.
Edit : ah ben non, je sais pas à quoi ressemble le fichier que tu lis. :blush: T’as un exemple à me filer ?

très simple :

5468

dans un fichier texte, par exemple “n”. Puis ./prho n

J’up pour demander simplement ce qui cloche avec ça :

[code]#include <gmp.h>

int main(int argc, char** argv){
mpz_t n;

mpz_init(n);
mpz_set_ui(n,12);
gmp_printf(“N=%Z\n”,n);

mpz_clear(n);
return 0;
}[/code]

On ne peut pas faire plus simple, et pourtant la seule sortie que j’ai est N=%…

Quelqu’un pourrait m’expliquer pourquoi ?

Pour gmp_printf, il faut indiquer également ce qu’ils appellent la “conversion” en plus du type de nombre GMP :

Voir : gmplib.org/manual/Formatted-Outp … ut-Strings

Pour les autres problèmes dans ton code, déjà j’arrive pas à piger le but de ton implémentation, y’a trop de trucs qui diffèrent par rapport à l’algo théorique (notamment la récursion). Qu’est-ce que tu as essayé de faire en introduisant ces modifs, précisément ?

J’essayais de donner tous les facteurs de N, pas seulement l’un d’entre eux.
Tant que j’aurais pas inclus des tests plus élaborés, je laisse la version qui ne donne qu’un facteur.

merci pour m’avoir mis les nez sur les erreurs bêtes que j’ai fais.

Bon, j’ai un truc qui marche apparemment bien. En vrac, quelques remarques par rapport à ton code d’origine (avant que tu ne supprimes la récursion)…

[ul][li] LE gros bug venait de la récursion : tu prenais le reste de la division (n mod d) au lieu du quotient (mpz_tdiv), et tu faisais l’appel récursif au mauvais endroit (la récursion ne doit avoir lieu que si on a trouvé un facteur).[/li]
[li] Pas besoin de passer les paramètres par référence, puisque la structure mpz_t est faite de telle manière que son passage en paramètre par copie ait le même effet qu’un passage par référence.[/li]
[li] mpz_get_ui() n’est pas sûr, il tronque le résultat si le nombre est trop grand (à priori ça n’avait pas d’incidence pour les valeurs que tu manipulais vu qu’elles étaient petites, mais dans l’absolu c’était un bug). Il vaut mieux créer une constante de type mpz_t qui servira pour les comparaisons avec la valeur 1.[/li]
[li] Aucun souci pour utiliser des noms de variables identiques dans diverses fonctions, le compilateur ne se mélange pas les pinceaux et c’est plus simple si les noms dans le code correspondent aux noms dans l’algo. :wink:[/li]
[li] Sinon, j’ai juste simplifié un peu ton code, et aussi rajouté une deuxième récursion pour essayer de décomposer un facteur en ses facteurs premiers. Le souci étant qu’il n’y a aucune garantie que les facteurs trouvés soient des facteurs premiers. Mis à part ce détail ça semble fonctionner.[/li][/ul]

[code]#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>

void f(mpz_t, unsigned int, mpz_t);
void prho(unsigned int, mpz_t);

// Global, utilisé pour les comparaisons avec la constante 1
// On ne peut pas simplement utiliser (mpz_get_ui(…) == 1) car :
// If op is too big to fit an unsigned long then just the least significant bits that do fit are returned.
// The sign of op is ignored, only the absolute value is used.
// Autrement dit, risque de collision pour les grands nombres.
mpz_t mpz1;

int main(int argc, char** argv)
{
FILE* file;
mpz_t n;
unsigned int i = 1;

mpz_init_set_ui(mpz1, 1);

if ((argc < 2) || (argc > 3) || !(file = fopen(argv[1], “r”)))
{
fprintf(stderr, “Usage: prho FICHIER [ENTIER]\n”);
exit(1);
}

mpz_init(n);
mpz_inp_str(n, file, 10);
fclose(file);
gmp_printf(“N = %Zd\n”, n);

if (argc == 3)
i = atoi(argv[2]);
prho(i, n);

mpz_clear(n);
mpz_clear(mpz1);
return 0;
}

// f(x) = (x^2 + c) mod n, c != 0,-2
void f(mpz_t x, unsigned int i, mpz_t n)
{
mpz_pow_ui(x, x, 2);
mpz_add_ui(x, x, i);
mpz_mod(x, x, n);
}

// rho de Pollard :
//
// Inputs: n, the integer to be factored; and f(x), a pseudo-random function modulo n
//
// Output: a non-trivial factor of n, or failure.
//
// 1. x ← 2, y ← 2; d ← 1
// 2. While d = 1:
// 1. x ← f(x)
// 2. y ← f(f(y))
// 3. d ← GCD(|x − y|, n)
// 3. If d = n, return failure.
// 4. Else, return d.
//
void prho(unsigned int i, mpz_t n)
{
mpz_t x, y, d, tmp;

mpz_init_set_ui(x, 2);
mpz_init_set_ui(y, 2);
mpz_init_set_ui(d, 1);
mpz_init(tmp);

while (mpz_cmp(d, mpz1) == 0)
{
// x ← f(x)
f(x, i, n);
// y ← f(f(y))
f(y, i, n);
f(y, i, n);

// d ← GCD(|x − y|, n)
mpz_sub(tmp, x, y);
mpz_abs(tmp, tmp);
mpz_gcd(d, tmp, n);

}

// on efface x, y et tmp avant la récursion
// ils ne sont plus utilisés, inutile qu’ils occupent des ressources
// pendant le reste des calculs, qui peut être assez long
mpz_clear(x);
mpz_clear(y);
mpz_clear(tmp);

// si on trouve un facteur…
if (mpz_cmp(d, n) != 0)
{
// on prend le quotient, pas le reste !!
// accessoirement, on effectue cette opération AVANT que la récursion
// sur “d” ne modifie ce dernier
mpz_tdiv_q(n, n, d);

// on essaye de trouver les facteurs premiers du facteur juste trouvé
// le choix (i + 2) est totalement arbitraire et devrait être amélioré
// (aucune assurance que "d" soit totalement décomposé en facteurs premiers)
// si on conserve simplement "i" sans modification, le facteur ne sera jamais décomposé plus avant
prho(i + 2, d);

if (mpz_cmp(n, mpz1) != 0)
  prho(i, n);

}
else
// on affiche “n” lorsqu’on n’a trouvé aucun facteur
// sachant que lorsqu’on trouve un facteur, à la fois ce facteur et le quotient
// sont décomposés récursivement, ça signifie que TOUS les facteurs
// non-décomposables vont passer par ce chemin et donc être affichés
gmp_printf(“F = %Zd\n”, n);

mpz_clear(d);
}[/code]

Super, merci… C’est du bon sens.

C’est intéressant le fait de pouvoir élaborer des structures de manière à pouvoir copier les valeurs sans les passer en référence. Merci de l’avoir pointé du doigt.

J’écris désormais le p-1, qui n’est pas non plus très compliqué, je prendrais en compte toutes tes remarques (dumoins j’essaierais :).

C’est une ruse de sioux en C : on déclare un typedef en tant que tableau (à un seul élément) de la structure concernée, et du coup le passage en paramètre se fait systématiquement par pointeur (puisqu’il s’agit d’un tableau). Pour accéder aux membres, il suffit d’utiliser la notation pointeur var->membre au lieu de la notation var.membre habituelle pour une structure pile (on pourrait aussi utiliser un index de tableau var[0].membre, ça correspondrait plus à la réalité mais le code serait moins lisible).

[code]#include <stdio.h>

struct ref_struct
{
int i;
};
typedef struct ref_struct ref_t[1];
// ou, plus concis (si on n’a pas besoin de la structure en elle-même) :
// typedef struct
// {
// int i;
// }
// ref_t[1];

void f(ref_t r)
{
++r->i;
}

int main()
{
ref_t r;
r->i = 1;
f®;
printf("%d\n", r->i);
return 0;
}[/code]
:wink:

Super !

Plus qu’à mettre en pratique pour que ça rentre…

Je passe rapidement pour mettre à jour le topic…

J’ai trouvé un nombre qui fait planter le programme
253199530752162813639052871138083720574153437161394896249854561312828455598591162347792026599

j’implémente un test de primalité pour passer outre, et terminer normalement.
Selon le test de Miller-Rabin, celui-ci est sûrement premier.

Outre Miller-Rabin, qui me semble le plus approprié, quelqu’un aurait des idées d’implémentation intéressantes ?