Pokazywanie postów oznaczonych etykietą wielomianowy algorytm faktoryzacji. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą wielomianowy algorytm faktoryzacji. Pokaż wszystkie posty

17 czerwca 2026

Wielomianowy algorytm faktoryzacji, rodzina odwracania działania

Mam już napisany program do faktoryzacji z rodziny rozwiązań odwracających działania. Nie wydaje się mi się bardzo szybki, ale to dlatego, że każę mu wypisywać w każdej iteracji drzewo możliwości, aby móc obserwować jego działanie. To wypisywane jest właśnie bardzo wolne. 

Algorytm do działania potrzebuje listy początkowych liczb pierwszych primes = [3,5,7,...] oraz iloczynów początkowych liczb pierwszych product = [6,30,210,...] = [2*3, 2*3*5, 2*3*5*7, ...]. Liczba pierwsza 2 nie jest uzwględniona w tablicy primes, ale w product już tak.
Tablice te w razie potrzeby trzeba rozszerzać, choć dla testowanych przypadków kilkunastucyfrowych wystarczy naledwie kilka liczb pierwszych oraz ich iloczynów. Rozszrzerzanie tych tablic w razie potrzeby o kolejną liczbę pierwszą, jej iloczyn z pozostałymi, stanowi część funkcji.

Zasada iteracji jest następująca: na drzewie mam już odłożone iloczyny a*b mające tę samą resztę co product[i-1]. Pobieram jeden. Rozszerzenie do iloczynu product[i] o liczbę pierwszą p ma specyficzną budowę:
a = a+k*product[i-1],  k=0..p-1
b = b+j*product[i-1],  j=0..p-1
Jest ich p^2. Spośród nich wybieram te a*b, które mają tę samą resztę co liczba rozkładaną n modulo product[i], oraz nie mogą być zbyt duże. Te odkładam do drzewa (a,b,i), chyba, że iloczyn pokrywa sie z n, wtedy następuje koniec funkcji, zwrócenie wyniku n = a*b. W drzewie trzymam dodatkowo indeks i, będący zarazem poziomem drzewa. Przycinanie przepuszcza maksymalnie do p wartości, i to tylko na niższych poziomach. Inne wersje algorytmów tej rodziny mogą mieć mocniejsze przycinanie, kosztem poziomów. Po przekroczeniu pewnej wartości pojawia się bardzo mało nowych węzłów wstawianych do drzewa. 

Inicjacja, gdy n modulo 6 jest 1, odkładam dwie pary (1,1, indeks 1), (5,5, 1), gdy n modulo 6 jest 5 odkładam (1,5, 1).
Dla liczb kilkunastocyfrowych poziom drzewa nie przekracza 10, a kiedy pisałem ten post, zanim zapisałem pierwsze dwa akapity, już uzyskałem rozkład:
9198491200007 = 197 * 109 * 428374759
poziom drzewa czyli długość tablic pomocniczych nie przekraczał 8. 

Podstawowa trudność spowalaniająca przy odkładaniu a*b była tą, że te same obliczenia były wykonywane także dla równoważnej pary b*a. Niepotrzebnie. Po wyłapaniu tych przypadków program zyskał zauważalnie na szybkości. Oraz osobno sprawdzam podzielność przez 2, 3.
Program lepiej działa dla większych wartości. Liczby gładkie mają zbyt dużo równoważnych iloczynów, co powoduje mocny rozrost drzewa. Z kolei liczby pierwsze i półpierwsze są rozpoznawane znacznie szybciej. 

Nie wiem dlaczego, ale intuicja podpowiada mi, że dopasowywanie par (a,b,) warto zacząć od większych wartości, a zmniejszać w kierunku wartości z poprzedniej iteracji odejmując product[i]. Elementy product[] mają wzrost wykładniczy, 


Odwracanie systemów o podstawach będących liczbami złożonymi, jak np. dziesiątkowego, mają zawsze tendencję do powolnego rozrostu drzewa wydającego się wykładniczym. Jednak ten wzrost wykładniczy jest dla podstawy systemu, czyli nie przekracza m^(log _m n) = n, dla binarnego 2^(lg n), gdzie lg oznacza logarytm binarny liczby. Najmocniejsze przycinanie będące porównaniem reszt po konwersji do wielokrotności podstawy praktycznie blokuje rozrost wykładniczy w wielu przypadkach, zostaje kilka ścieżek prowadzących do różnych czynników. Z tego powodu ośmielam się nazywać tę rodzinę algorytmów wielomianowymi.
Praktycznie, kiedy ktoś mi podsuwa klucz publiczny RSA, jeżeli ostatnią 'cyfrą' jest 0xF, to nie ma innych możliwości modulo 8 niż iloczyny 0x3*0x5 albo 0x1*0x7.

30 marca 2026

Wielomianowy algorytm faktoryzacji, jeden z wolniejszych

 W rodzinie faktoryzacji zwiazanych z rozwinięciem liczby N w szereg odkryłem sposób, który ma bardzo specyficzne wartości, i jest łatwy do wyznaczenia złożoności. 

Niech p[k] oznacza k-tą liczbę pierwszą. Współczynniki rozwinięcia w szereg są iloczynami początkowych kolejnych liczb pierwszych oraz 
2*3*5*...*p[k] <= N < 2*3*5*...*p[k]*p[k+1] .
Rozwinięcie w szereg polega na odejmowaniu kolejnych wielokrotności w[k] iloczynów początkowych liczb pierwszych, z których każdy kolejny powstaje przez mnożenie przez coraz mniej wartości, kolejne iloczyny powstają przy zmniejszaniu się k, przedostatni byłby 2*3=6, ostatni przez 2. 

Każdy taki iloczyn zabiera jak największą wartość nieujemną z N generując współczynniki w[i], i<k. Wtedy N miałoby przedstawienie:
N = 2*...*p[k]*w[k] + 2*...*p[k-1]*w[k-1] + ... + 2*3*w[2] + 2*w[1] ,
tylko - możemy zacząć od liczby pierwszej d=p[k+1], sprawdzamy, który z iloczynów 2*...*p[i] > d, oraz pozostałe łączymy w jedną wartość r zapisaną w systemie o podstawie p[k+1]. Podobnie iloczyn 2*...*p[i] zapisujemy w systemie o podstawie d=p[k+1], a pozostałe -- wystarczą tylko współczynniki w[i], ..., w[k] wyznaczane w tej kolejności. Wartości te będziemy wyliczali po drodze, każdy mnożony przez małą liczbę pierwszą p[k-j] dla j=k-i+1 malejące do 0. 

Szereg ma tę własność, że jeśli modyfikowane d będzie liczbą p[i]-gładką, to często począwszy od wyrazu i+1 uzyskamy 0 lub wielokrotność liczby pierwszej, które będzie sie propagowało w kierunku w[k]. Podzielność przez początkowe liczby pierwsze sprawdzimy np. bezpośrednio licząc największy wspólny dzielnik iloczynu 2*...*p[k] z N. 

Zatem pamiętamy: współczynniki w[k],...,w[i] rozwinięcia w szereg z przypisanymi im liczbami pierwszymi, liczby p[i] oraz r, zapisane w systemie o podstawie d, samo d.
Szukamy wartości d, dla której wartość szeregu przystaje do 0 modulo d. Wszystkie współczynniki są małymi liczbami, tylko p[i], d oraz r są większe. Aby znaleźć wartość reszty N modulo d, reszta z dzielenia przez 2*...*p[i] jest dana jawnie, zaś by uzyskać resztę z dzielenia przez 2*...*p[i]*p[i+1] mnożymy ją przez p[i+1] modulo d. Kontynujemy aż do p[k]. Wyznaczone reszty w kombinacji liniowej z w[j] liczone modulo d dają resztę N%d.
Jeśli iloczyn 2*...*p[i] = 1*d+1, na końcu iteracji przemieszczamy go do reszty r = r+w[i]*(d+1), oraz zaczynamy z wcześniejszym iloczynem
w[i+1] * (2*...*p[i] * p[i+1]) = w[i+1] * (p[i+1]*d + p[i+1])
pamiętając od teraz tę wartość. 

Przebieg iteracji: zwiększamy d+2, konwersje p[i] oraz r postaci
a*(d-2)+b = a*d+(b-2a)
z możliwym przeniesieniem a*d+b = (a-1)*d+(d+b), wyznaczamy współczynniki u[j]=2*...*p[j] modulo d dla i<j<=k, obliczamy kombinację liniową sum _{k>=j>=i} w[j]*u[j] i przyrównujemy ją do 0. wszystko na stosunkowo małych liczbach ograniczonych przez d, zatem złożoności maksymalnej obliczeń O(log n).
Wyrazów nie jest zbyt dużo. Dla RSA 200 okazało się, że wystarczy mniej niż 95 kolejnych liczb pierwszych, które powoli przemieszczają sie do r, czyli jakieś O(log log n) dla wyznaczenia wartości kombinacji liniowej. Tłumienie wartosci w r jest jak na modyfikacje wyrazów jest szybsze choć liniowe - staje się stałą wartością mniejszą od d.

Jeśli zauważymy, że musimy z d dla liczby N pierwszej dotrzeć do pierwiastka kwadratowego sqrt N, nasuwa się złozoność wykładnicza. Policzymy inaczej, kryterium stopu nie jest tutaj widoczne, powstaje, gdy suma szeregu rozbieżnego zaczynajacego się w p[i+1]^2 o wyrazie 4*d+4 staje sie większa niż N. Takich sumowań jest mniej niż N, zaś, jeśli N ma dzielniki (wszystkie większe niż p[i]), krotność iteracji jest zmniejszonym o p[i+1] rzędem najmniejszego z dzielników właściwych w grupie addytywnej, zatem O(n).
Podobnie jest w teście pierwszości AKS, gdzie w analogicznej grupie muliplikatywnej (małe twierdzenie Fermata) można czasem otrzymać rząd dzielnika równy N. Tu nawet nie docieramy to tak duzej wartości.

Łącznie szacuję złożoność na O(n * log n * log log n). Wielomianowe. 


Lecz algorytm jest jednym z wolniejszych, mimo że ma automatyczne ograniczenie stosowanych wielkości. Niewidoczne jest kryterium stopu. Propagowanie reszt powoduje, że niejako 'przy okazji' rozpoznajemy część wartości d jako złożonych, przy których kombinacja liniowa i tak nie będzie zerem choćby ze względu na dodatnie r.
Inną ciekawostką jest bardzo mało 'pamiętania', współczynniki w[] są wielkości liczb pierwszych, tylko dwie 'większe' wartości 'dwucyfrowe', z których używamy 'cyfr jedności' oraz podstawa systemu odpowiadająca dziesiątce d może osiągać wartość sqrt N -- wtedy lepiej jest stosować inny algorytm tej rodziny. Uzyska mniejszą złożoność dla tak dużych d, dokładniej już od d approx root[3] N inne algorytmy mają mniej prostszych obliczeń.  

Praktyczny zapis szeregu przy d=3:
7387= 3*[210*11] + 2*[30*7] + 1*[6*5] + 1*[2*3+0] + 1
w[] = [1, 1, 2, 3], r=1.