Autor teksta: Velimir Kovačić

Uvod

CRYSTALS–Dilithium

CRYSTALS–Dilithium postkvantni je algoritam digitalnog potpisa čija je težina utemeljena na težini pronalaženja najkraćih vektora u rešetkama (engl. lattice). Sama shema napravljena je po principu Fiat–Shamir with Aborts.

Matematička pozadina

Sve operacije s polinomima izvodit će se na prstenu polinoma Prsten polinoma . Taj prsten sadrži sve polinome oblika Raspis polinoma, za koje vrijedi da im je stupanj manji od n, a koeficijenti manji od q što se osigurava time da se zbrajanje i množenje koeficijenata odvija pod mod q. Dakle: Definicija prstena polinoma

Koristimo vrijednosti q=2^23–2^13+1=8380417, n=256.

R predstavlja isti prsten polinoma kao Rq, ali nema ograničenja na veličinu koeficijenata.

Maksimalni koeficijent polinoma je: Definicija maksimalnog koeficijenta polinoma

Gdje je w_i i-ti koeficijent polinoma w, a mod +- centrirani modulo alfa za alfa > 0 :

Centrirani modulo

Centrirani modulo definicija

S_η predstavlja skup svih elemenata iz prstena polinoma R kojima je maksimalni koeficijent polinoma manji od η : S_η definicija

S_η tilde predstavlja skup svih elemenata kao S_η , ali bez onih koji imaju koeficijente jednake η : S_η definicija

Vektori će se označavati masnim malim slovima, matrice masnim velikim slovima, polinomi ukošenim latiničnim slovima, a brojevi ukošenim grčkim slovima.

Fiat–Shamir with Aborts

GenerirajKljučeve():
      ARqk×l
      (s1, s2) ← Sηl × Sηk
      t := As1 + s2
      pk := (A, t)
      sk := (A, t, s1, s2)
      vrati (pk , sk) 


Potpiši(sk, M):
      z := ⊥
      
      dok je z = ⊥:
            y˜Sγ1l
            w := Ay
            w1 := HighBits(w, 2γ2)
            cBτ := H(Mw1)
            z := y + cs1

            ako jezγ1β ili ‖LowBits(Aycs2, 2γ2)‖ ≥ γ2β:
                 z := ⊥
      vrati σ = (z, c)


ProvjeriPotpis(pk, M, σ = (z, c)):
      w'1 := HighBits(Azct, 2γ2)
      
      ako jez < γ1β i c == H(μw'1):
            vrati potpis je valjan
      inače:
            vrati potpis nije valjan
          

U funkciji GenerirajKljučeve(), koja ne prima nikakve argumente, prvo se stvori matrica A veličine k×l u čijoj je svakoj ćeliji polinom iz prstena polinoma Rq. Zatim se nasumično generiraju 2 vektora polinoma s1 duljine l i s2 duljine k, tako da su koeficijenti svih polinoma najviše η. Vektor polinoma t dobije se množenjem matrice A s vektorom polinoma s1 i pribrajanjem vektora polinoma s2. Javni ključ pk sastoji se od matrice A i vektora polinoma t, a privatni ključ sk sastoji se od matrice A, vektora polinoma t, vektora polinoma s1 i vektora polinoma s2. Funkcija vraća javni i privatni ključ.

Funkcija Potpiši(sk, M) kao ulazne argumente prima privatni ključ sk i poruku M. Vektor polinoma z postavi se na vrijednost ⊥ što predstavlja vrijednost none. Petlja se provodi dok je vektor polinoma z jednak ⊥. Vektor polinoma y duljine l nasumično se generira tako da su mu svi koeficijenti manji od γ1. w je vektor polinoma jednak umnošku matrice polinoma A i vektora polinoma y. w1 je vektor polinoma, koji se dobije uzimanjem samo visokih bitova svih koeficijenata svih polinoma vektora w. Koeficijenti se rastavljaju na više i niže bitove prema formuli w = w1 · 2γ2 + w0, w1 predstavlja više, a w0 niže bitove, vrijedi |w0| ≤ γ2. c iz Rq je takav polinom da ima točno τ koeficijenata jednakih 1, dok su svi ostali jednaki 0. Dobije se primjenom hash funkcije nad konkatiniranom porukom M s vektorom polinoma w1. Vektor polinoma z, koji predstavlja potencijalni digitalni potpis, računa se kao zbroj vektora polinoma y i umnoška polinoma c s vektorom polinoma s1. Ako je najveći koefcijent polinoma vektora polinoma z veći ili jednak γ1β ili su niski bitovi Aycs2 veći ili jednaki γ2β, vektor polinoma z postavlja se na početnu vrijednost ⊥ te se tako ponavlja petlja. Kada se pronađe vektor z i polinom c takvi da zadovolje uvjete vraća se potpis σ koji sadrži samo njih.

Za provjeru ispravnosti digitalnog potpisa koristi se funkcija ProvjeriPotpis(pk, M, σ = (z, c)) koja kao argumente prima javni ključ pk, poruku M i digitalni potpis σ. Vektor polinoma w'1 računa se kao visoki bitovi od Azct.

Ukoliko je potpis ispravan vrijedi:
w'1 = HighBits(Azct, 2γ2) = HighBits(A(y + cs1) – c(As1 + s2), 2γ2) = HighBits(Aycs2, 2γ2) = HighBits(Ay) = HighBits(w) = w1.

Ako je w'1 jednak w1 iz funkcije Potpiši(sk, M), hash koji daje konkatiniran uz M biti će jednak polinomu c. Uz to se provjerava i svojstvo ‖z < γ1β, koje ima svaki vektor polinoma z koji se pojavi kao dio digitalnog potpisa na izlazu funkcije Potpiši(sk, M). Ako su ova dva uvjeta zadovoljena digitalni je potpis ispravan. Funkcija će kao izlaz dati podatak o ispravnosti.

Dilithium poboljšanja

Algoritam CRYSTALS–Dilithium na osnovni princip Fiat–Shamir with Aborts uvodi određena poboljšanja i optimizacije.

Spremanje matrice polinoma A

Izravno pohranjivanje matrice A zauzimalo bi puno memorije jer se sastoji od k×l polinoma stupnjeva do 255. Umjesto toga, sprema se seed ρ iz kojeg se hash funkcijom matrica A generira kada god je to potrebno.

Spremanje vektora polinoma t

Problem s vektorom polinoma t je što zauzima mnogo memorije, a pohranjuje se i u javnom i u privatnom ključu. Budući da rezultat Azct ne ovisi previše o nižim bitovima koeficijenata polinoma iz vektora t možemo ih isključiti iz javnog ključa, to jest spremati samo više bitove t. Nastaje problem u tome što sada ne možemo uvijek točno izračunati Azct jer ne uključujemo niže bitove t-a iz kojih bi se mogli pojaviti prijenosi. To rješavamo stvaranjem vektora hintova h u kojem pohranjujemo informacije o prijenosima.

Determinizam

Dodaje se mogućnost determinističkog generiranja potpisa determinističkim generiranjem vektora polinoma y što će ujedno biti i zadana postavka.

Oblik polinoma NTT

Zahvaljujući Kineskom teoremu o ostacima možemo podijeliti prsten polinoma Rq na manje prstene polinoma koji u umnošku daju polinom jednak onom koji smo dijelili, istu stvar možemo i s polinomima unutar tog prstena. Pretvaranje polinoma u taj oblik naziva se NTT (eng. Number Theoretic Transform). Funkciju pretvaranja iz kanonskog u oblik NTT označavamo s NTT(), a funkciju za pretvorbu iz oblika NTT u kanonski oblik zovemo NTT-1(). Polinomi u obliku NTT mogu se pomnožiti u vremenskoj složenosti O(n), a pretvorba u oblik NTT odvija se u vremenskoj složenosti O(log⁡n). Dakle, umjesto množenja polinoma u kanonskom obliku u vremenskoj složenosti O(n2), možemo ih pomnožiti u vremenskoj složenosti O(nlog⁡n).

Polinomi u obliku NTT biti će označeni kapicom.

NTT izrazi


Algoritam

Važne funkcije

Hashing to a Ball

Funkcija SampleInBall(ρ) vraća jedan element iz skupa Bτ.

Bτ predstavlja skup svih elemenata iz R koji imaju točno τ koeficijenata koji su ili 1 ili -1, dok su svi ostali koeficijenti 0. Takvih elemenata u R ima 2^τ(256, τ).

Za funkciju SampleInBall(ρ) potreban je hash koja hashira na skup Bτ u 2 koraka:

  1. Second pre-image resistant hash funkcija koja mapira niz bitova proizvoljne duljine na niz bitova duljine 256.
  2. Extendable-output (XOF) hash funkcija koja koristi izlaz 1. koraka kao seed.

SampleInBall(ρ):
      c := c0c1...c255 = 00...0
      za i := 256 - τ do 255
      j := broj iz skupa {0,1,...,i} odabran koristeći XOF funkciju
      s := broj iz skupa {0,1} odabran koristeći XOF funkciju
      ci := cj
      cj := (-1)s
      vrati c
            

Ekspanzija matrice A

ExpandA(ρ)

Kako se u ključevima ne bi morala pohranjivati cijela vrijednost matrice A, pohranjuje se seed ρ i svaki put kada je potrebno generira se matrica  u obliku polinoma NTT.

Generiranje vektora y

ExpandMask(ρ', κ)

Koristeći seed ρ i nonce κ, ova funkcija generira vektor duljine l koji se sastoji od polinoma iz Sγ1.

Hash otporan na kolizije

CRH(δ)

Hash otporan na kolizije mapira ulaz δ u niz bitova duljine 384.

Rastavljanje brojeva

Power2Roundq

Power2Roundq(r, d):
      r := r mod+q
      r0 := r mod±2d
      vrati ((r - r0)/2d, r0)

Za binarni broj r duljine n bitova, funkcija Power2Roundq(r, d) vraća par u kojem je prvi član viših n - d bitova, a drugi član nižih d bitova. Vrijedi: r = [(r - r0 )/2d] · 2d + r0.

Decomposeq

Decomposeq(r, α):
      r := r mod+q
      r0 := r mod±α
      ako je rr0 = q – 1:
            r1 := 0
            r0 := r0 − 1
      inače:
            r1 := (rr0)/α
      vrati (r1, r0)

Slično kao funkcija Power2Roundq(r, α), funkcija Decomposeq(r, α) rastavlja binarni broj na više i niže bitove (r1 i r0) i vraća ih. Koristeći α (parni djelitelj broja q-1) dijeli ih na način da vijedi jednakost: r = α · r1 + r0.

HighBitsq i LowBitsq

HighBitsq(r, α):
      (r1, r0) := Decomposeq(r, α)
      vrati r1
LowBitsq(r, α):
      (r1, r0) := Decomposeq(r, α)
      vrati r0

Pomoćne funkcije koje služe za dohvaćanja viših i nižih bitova koje vraća funkcija Decomposeq(r, α).

Hintovi

MakeHintq

MakeHintq(z, r, α):
      r1 := HighBitsq(r, α)
      v1 := HighBitsq(r + z, α)
      ako r1 == v1:
            vrati 0
      inače:
            vrati 1

Cilj stvaranja hinta je da ne moramo spremati cijeli broj nego samo njegove visoke bitove, a hint će nam otkriti postoji li prijenos pri zbrajanju. Konkretno, ispituje postoji li prijenos s nižih bitova pri zbrajanu r sa z.

UseHintq

UseHintq(h, r, α):
      m := (q - 1)/α
      (r1, r0) := Decomposeq(r, α)
      ako h = 1 i r0 > 0:
            vrati (r1 + 1) mod+m
      inače ako h = 1 and r0 ≤ 0:
            vrati (r1 − 1) mod+m
      inače:
            vrati r1

Funkcija UseHintq(h, r, α) na temelju hinta h koji može biti 0 ili 1 ispravlja i vraća više bitove broja r.

Leme vezane uz hintove

  1. UseHintq(MakeHintq(z, r, α), r, α) = HighBitsq(r + z, α)
    • Stvara se hint na temelju zbroja z i r i koristi se na visokim bitovima r što je jednako visokim bitovima zbroja r i z.
  2. Ako ‖s‖β i ‖LowBitsq(r, α)‖ < α/2 − β vrijedi HighBitsq(r, α) = HighBitsq(r + s, α)
    • Ova lema daje dovoljan uvjet za slučaj u kojem se neće mijenjati viši bitovi r kada mu se pribroji s.
  3. Neka je (r1, r0) = Decomposeq(r, α) i (w1, w0) = Decomposeq(r + s, α) i ‖s‖β vrijedi: ‖s + r0 < α/2 − β ako i samo ako w1 = r1 iw0 < α/2 − β

Važni parametri

U ovisnosti o NIST razini sigurnosti koriste se različite vrijednosti ključnih parametara.

NIST razina sigurnosti
Parametri 2 3 5
q [modulo] 8380417 8380417 8380417
d [broj nižih bitova] 13 13 13
τ [broj ±jedinica u c-u] 39 49 60
γ1 [maks. razina koef. u y] 217 219 219
γ2 [razina zaokruživanja] (q - 1)/88 (q - 1)/32 (q - 1)/32
(k, l) [dimenzije matrice A] (4, 4) (6, 5) (8, 7)
η [razina privatnog ključa] 2 4 2
β [τ · η] 78 196 120
ω [maks. broj jedinica u h] 80 55 75

Generiranje ključeva

GenerirajKljučeve():
      ζ ← {0, 1}256
      (ρ, ς, K) ∈ {0, 1}256×3 := H(ζ) 
      (s1, s2) ∈ Sηl × Sηk := H(ς)
      
      Â := ExpandA(ρ)
      t := NTT-1(Â · NTT(s1)) + s2 
      (t1, t0) := Power2Roundq(t, d)
      tr ∈ {0, 1}384 := CRH(ρt1)
      
      pk := (ρ, t1)
      sk := (ρ, K, tr, s1, s2, t0)
      vrati (pk, sk)

Funkcija GenerirajKljučeve() na ulazu ne prima ništa.

Prvo se generira ζ, nasumični 256-bitni broj. Na temelju hasha od ζ se generiraju brojevi ρ, ς i K. H je funkcija hash SHAKE-256. Hashiranjem ς generiraju se vektori: s1, duljine l i s2, duljine k. Koeficijenti ovih vektora su polinomi iz Sη. Iz broja ρ generira se i privremeno sprema  (oblik NTT matrice A). Svako polje matrice A polinom je iz prstena polinoma Rq, a sama matrica veličine je k×l, dakle ARqk×l. Računa se vektor polinoma t koristeći pretvorbu NTT te se potom dijeli na više bitove t1 i niže bitove t0 funkcijom Power2Roundq(t, d).

Hash koji na ulazu prima konkatiniran broj ρ i t1 (više bitove vektora t) na izlazu daje broj tr.

Javni ključ pk sastoji se od broja ρ i t1 (viših bitova vektora t). Privatni ključ sk sastoji se od brojeva ρ, K i tr i vektora s1, s2 i t0 (viših bitova vektora t).

Funkcija vraća javni i privatni ključ, sk i pk.

Potpisivanje

Potpiši(sk, M):
      Â := ExpandA(ρ)
      μ ∈ {0, 1}384 := CRH(trM)
      κ := 0
      (z, h) := ⊥
      ρ' ∈ {0, 1}384 := CRH(Kμ)	(ili ρ' ← {0, 1}384)
      
      ŝ1 := NTT(s1)
      ŝ2 := NTT(s2)
      0 := NTT(t0)
      
      dok je (z, h) == ⊥:
            y ∈ ˜Sγ1l := ExpandMask(ρ', κ)
            w := NTT−1(Â · NTT(y))
            w1 := HighBitsq(w, 2γ2)
            ˜c ∈ {0, 1}256 := H(μw1)
            ĉ := NTT(SampleInBall(˜c)) 
            z := y + NTT-1(ĉ · ŝ1)
            r0 := LowBitsq(w − NTT-1(ĉ · ŝ2), 2γ2)
      
            ako jezγ1 - β ilir0γ2 - β:
                  (z, h) := ⊥
            inače:
                  h := MakeHintq(−NTT-1(ĉ · 0), w − NTT-1(ĉ · ŝ2) + NTT-1(ĉ · 0), 2γ2)
                  ako je ‖NTT-1(ĉ · 0) ‖γ2  ili  (broj jedinica u h) > ω: 
                        (z, h) := ⊥
            κ = κ + l
      
            vrati σ = (z, h, ˜c)

Funkcija Potpiši(sk, M) na ulazu prima tajni ključ sk i poruku M koja se potpisuje.

Iz broja ρ generira se i privremeno sprema  (oblik NTT matrice A). μ se generira kao 384-bitni broj iz hasha otpornog na kolizije (CRH) koji na ulazu prima konkatinirani broj tr iz tajnog ključa i poruku M. Broj κ postavlja se na 0, a par vektora (z, h) postavljaju se na vrijednost ⊥ (none/ništa). U slučaju determinističkog potpisivanja ρ' se generira kao 384-bitni broj iz hasha otpornog na kolizije (CRH) koji na ulazu prima konkatinirani broj K iz tajnog ključa i prethodno generirani broj μ. U slučaju nedeterminističkog potpisivanja ρ' se generira kao nasumični niz bitova.

Izračunaju se oblici NTT vektora polinoma s1, s2 i t0.

Provodi se petlja dok je par vektora (z, h) jednak ⊥. Na temelju ρ' i κ generira se vektor polinoma duljine l, a svaki njegov polinom ima koeficijente manje od γ1. Vektor polinoma w izračuna se kao umnožak matrice A i vektora y koristeći pretvorbu NTT. Vektor w1 dobije se primjenjujući HighBitsq funkciju uz α = 2γ2 na vektor w. ˜c, 256-bitni broj dobije se hashiranjem kontkatiniranog broja μ s vektorom polinoma w1. Polinom c dobiva se funkcijom SampleInBall kojoj se kao ulazni argument prosljeđuje broj ˜c. Vektor polinoma z dobiva se sumiranjem vektora polinoma y i umnoška polinoma c s vektorom polinoma s1. Množenje se provodi koristeći pretvorbe NTT. Vektor polinoma r0 jednak je razlici funkciji nižih bitova razlike w - cs2 (množenje pretvorbama NTT) uz α = 2γ2.

Ako je najveći koeficijent polinoma u vektoru polinoma z veći ili jednak γ1β ili je najveći koeficijent polinoma u vektoru polinoma r0 veći ili jednak γ2β, postavljamo par (z, h) na početnu vrijednost ⊥.

Inače stvaramo vektor hintova h funkcijom MakeHintq za zbroj −ct0 i wcs2 + ct0. Ako je pak najveći koeficijent polinoma u vektoru polinoma umnoška ct0 veći ili jednak γ2 ili je broj jedinica u vektoru hintova h veći od ω, postavljamo par (z, h) na početnu vrijednost ⊥.

Na kraju svakog ponavljanja broju κ dodajemo broj l.

Kada se uspije stvoriti zadovoljavajući par (z, h), vraća se potpis σ koji se sastoji od vektora polinoma z, vektora h i broja ˜c.

Provjera potpisa

ProvjeriPotpis(pk, M, σ = (z, h, ˜c)):
      Â := ExpandA(ρ)
      μ ∈ {0, 1}384 := CRH(CRH(ρt1) ‖ M)
      c := SampleInBall(˜c)
      w'1 := UseHintq(h, NTT-1(Â · NTT(z) – NTT(c) · NTT(t1 · 2d)), 2γ2)
      
      ako jez < γ1β i ˜c == H(μw'1) i (broj jedinica u h) ≤ ω:
            vrati potpis je valjan
      inače:
            vrati potpis nije valjan

Iz broja ρ generira se i privremeno sprema Â. Broj μ generira se kao i u digitalnom potpisu iz konkatiniranacije broja tr (koji se izračunava iz ρ i t1 kao u funkciji GenerirajKljučeve()) i poruke M. c se izračuna funkcijom SampleInBall iz ˜c. Iskorištava se vektor hintova h iz potpisa na razliku Azct1 · 2d funkcijom UseHintq te se rezultat sprema kao w'1. Kao i do sada množenje se odvija uz pretvorbe NTT.

Za vektor polinoma z i vektor hintova h provjeravaju se uvjeti iz funkcije Potpiši(sk, M), a izračunati broj w'1 se provjerava preko hash funkcije iz čega se donosi odluka o ispravnosti potpisa.


Program za testiranje

Uvod

Program je namijenjen isprobavanju digitalnog potpisivanja i provjere digitalnog potpisa postkvantnim algoritmom CRYSTALS-Dilithium. Izvorni kod samog algoritma može se preuzeti na web-stranici NIST-a, korištena je preporučena referentna implementacija dilithium3. Osim digitalnog potpisa postoji dodatna funkcionalnost enkripcije.

Demonstracija

Program se može pokrenuti na Linuxu iz naredbenog retka pozicioniranjem unutar vršnog direktorija i pozivom naredbe: $ python main.py.

Početni prozor

Na početnom prozoru možemo odabrati jednu od dvije opcije pritiskanjem na gumb: digitalni potpis ili enkripcija.

Prozor za digitalni potpis

Na prozoru za digitalni potpis možemo unijeti proizvoljnu poruku u za to predviđenom okviru za unos, zatim možemo generirati par ključeva (javni i privatni) čijih će se prvih nekoliko bajtova prikazati na ekranu.

Nakon što smo generirali ključeve možemo generirati digitalni potpis na temelju privatnog ključa i poruke pritiskom na gumb „Generiraj digitalni potpis“.

Pritiskom na gumb „Provjeri digitalni potpis“ provjeriti će se digitalni potpis na temelju javnog ključa, poruke i samog digitalnog potpisa ispisat će se poruka „Ispravan digitalni potpis“ ili „Neispravan digitalni potpis“ ovisno o ispravnosti te trojke.

Pritiskom na gumbe s ikonom olovke pored javnog ključa, privatnog ključa i digitalnog potpisa otvorit će se prozor za pregled i izmjenu bajtnog zapisa u heksadekadskom formatu. Može se izmijeniti i spremiti (samo ako se broj bajtova i format ne narušava) pritiskom na gumb „Spremi“ ili odustati od izmjena i izaći pritiskom na gumb „Izađi“.

Prozor za enkripciju

Ponašanje je vrlo slično kao na prozoru za digitalni potpis, može se upisati poruka, generirati par ključeva, ali umjesto potpisa, kriptira se poruka, a umjesto provjere potpisa je dekripcija poruke. Pritiskom na gumb sa znakom upitnika otvara se prozor za pregled bajtnog zapisa kriptirane poruke u heksadekadskom formatu.

Implementacija

Kao prvi korak potrebno je generirati dijeljene biblioteke libpqcrystals_aes256ctr_ref.so, libpqcrystals_fips202_ref.so i libpqcrystals_dilithium3_ref.so pozivom naredbe $ make shared iz izvornog koda CRYSTALS-Dilithiuma. Zatim je potrebno generirati dodatnu dijeljenu biblioteku randombytes.so naredbom $ cc -fPIC -shared -o randombytes.so randombytes.c.

Dijeljene biblioteke su unesene kroz python datoteku dilithium.py u obliku DLL-ova.

Napravljene su funkcije koje tipove podataka bliske pythonu pretvaraju u one bliske jeziku C i pozivaju funkcije dijeljenih biblioteka.
U datoteci main.py nalazi se kod potreban za grafičko sučelje programa i barata se s python tipovima podataka koji se prosljeđuju funkcijama iz dilithium.py kako bi se dobili željeni rezultati.

Literatura