Bir önceki konuda (\(\textbf{Nokta Toplama}\)) iki farklı noktayı nasıl toplayacağımızı öğrendik. Formüllerin kalbi şuydu: iki noktadan geçen doğrunun eğriye kesişimini bul, yansımasını al. Ancak o formüllerin önünde görünmez bir kısıtlama vardı: iki nokta farklı olmalıydı, yani \(x_1 \neq x_2\). Peki ya aynı noktayı kendisiyle toplamak istersek? Yani \(P + P = 2P\) hesabını?
Bu soru, görünüşte basit bir özel durumu tanımlar; ama aslında nokta katlama, eliptik eğri kriptografisinin en kritik yapı taşıdır. Bir gizli anahtar \(k\), büyük olasılıkla 256 bitlik bir tam sayıdır ve \(Q = k \cdot G\) hesabı binlerce nokta katlaması içerir. Bitcoin'in bir anahtar türetmesi, TLS'in oturum anahtarı üretmesi, Ethereum'un bir imzalaması — hepsinin merkezinde nokta katlama vardır. Bu işlemi anlamak, skaler çarpımı ve dolayısıyla tüm ECC protokollerini anlamak demektir.
Nokta katlamayı nokta toplamadan ayrı bir formülle çözmek zorunda olmamız, tamamen geometrik bir zorunluluktan kaynaklanır. İki farklı nokta için "ikisi arasından geçen doğru" diye bir şey vardır. Ama tek bir nokta için bu tanım anlamsızlaşır. Yerine geçen kavram, o noktada eğriye çizilen teğet doğrusudur. Bu teğetin eğriye ikinci kez kesiştiği nokta ve ardından yapılan yansıma, \(2P\) sonucunu verir.
Neden Teğet? Toplama Limitinin Geometrik Açıklaması
Geometrik sezgiyi kuralım. \(P + Q\) toplamının tanımını hatırlayın: \(P\) ve \(Q\) arasından geçen doğruyu çiz, eğriyle üçüncü kesişim noktasını bul, yansımasını al. Şimdi \(Q\)'yu yavaş yavaş \(P\)'ye yaklaştırdığınızı düşünün. \(Q \to P\) limitinde, iki farklı noktadan geçen doğru ne olur? Bu iki nokta birbirlerine sonsuz yaklaştıkça, aralarından geçen kesen doğru (secant line), P noktasındaki teğet doğrusuna dönüşür.
Matematiksel olarak bu, diferansiyel hesabın temel tanımıyla örtüşür: bir eğrinin bir noktasındaki teğeti, o noktaya yaklaşan iki nokta arasındaki kesen doğrunun limitidir. Dolayısıyla nokta katlama, limit anlamında nokta toplamadan doğar — ayrı bir kural değil, aynı geometrinin özel bir durumudur. Bu bakış açısı, grup yapısının tutarlılığını da garanti eder: grup aksiyomları \(P + P\) için de bozulmadan geçerlidir, çünkü bu işlem içsel olarak aynı geometrik çerçeveden türetilmiştir.
İki farklı nokta \(P = (x_1, y_1)\) ve \(Q = (x_2, y_2)\) arasındaki kesenin eğimi: \[m_{kesen} = \frac{y_2 - y_1}{x_2 - x_1}\] \(Q \to P\) limitini aldığımızda bu, L'Hôpital kuralıyla veya implicit diferansiyasyonla eğrinin \(P\) noktasındaki türevine dönüşür. Weierstrass denklemi \(y^2 = x^3 + ax + b\)'nin her iki tarafını \(x\)'e göre diferansiye edersek teğetin eğimini buluruz — bu, nokta katlama formülünün özüdür.
İmplicit Diferansiyasyon ile Teğet Eğiminin Türetimi
Weierstrass denklemini analitik olarak çözmek yerine — yani \(y\)'yi \(x\)'in açık bir fonksiyonu olarak yazmak yerine — her iki tarafı doğrudan \(x\)'e göre diferansiye ediyoruz. Bu yönteme implicit diferansiyasyon denir ve eğrinin her noktasında teğetin eğimini verir.
Katsayılar (a, b) eğrinin şeklini belirler ve tekil olmama şartını (\(4a^3 + 27b^2 \neq 0\)) sağlar.
x'e bağlı türevi alarak teğet doğrusunun eğim fonksiyonuna giden yolu açarız.
\(\frac{dy}{dx}\) terimi, y koordinatının x'e göre değişim oranını (teğet eğimini) temsil eder.
Bu formül, nokta katlama aritmetiğinde eğimi (slope) doğrudan hesaplamamızı sağlar.
Bu formül, nokta katlama işleminin matematiksel kalbidir. Dikkat edilmesi gereken iki kritik durum şimdiden görünür olur:
- Payda \(2y = 0\) ise: Bu, \(y = 0\) anlamına gelir. Eğri üzerinde y-koordinatı sıfır olan noktalar, x-eksenini tam olarak keser ve eğrinin bu noktalarda teğeti dik (dikey) bir doğrudur. Dolayısıyla \(2P = \mathcal{O}\) (sonsuz nokta) olur. Bu noktalar mertebesi 2 olan noktalardır — onlar hakkında bir sonraki bölümde ayrıntılı duracağız.
- Pay \(3x^2 + a = 0\) ise: Teğet yatay (\(m = 0\)) olur. Bu, teğetin eğriye ikinci kez kestiği noktanın, aynı y-koordinatında tam tersinde yer aldığı anlamına gelir. Yatay teğet, eğrinin yerel bir minimum ya da maksimumunu işaret eder ve \(2P\)'nin özel bir konumda bulunduğunu gösterir.
Sonlu alanlarda (\(\mathbb{F}_p\)) bu türev formülü aynen kullanılır, ancak bölme işlemi yerine çarpımsal ters kullanılır. Gerçek sayılardaki sürekli eğri sezgisi tamamen geçerliliğini korur; yalnızca aritmetik modüler hale gelir.
Katlama Formülleri: \(2P\) Koordinatlarının Hesabı
Sonlu alanda bölme yerine Fermat'ın Küçük Teoremi veya Genişletilmiş Öklid kullanılır.
Normal nokta toplamındaki \(x_3 = m^2 - x_1 - x_2\) kuralı ile tam bir geometrik tutarlılık içindedir.
Bu yansıma, eliptik eğri grubunun ters eleman (\(P + (-P) = \mathcal{O}\)) kuralını sağlar.
Bu adım nokta toplama ile birebir aynıdır: teğet doğrusunun \(x_3\) noktasındaki değerini hesaplayıp x-eksenine yansıtıyoruz (negatifini alıyoruz). Üç formül bir arada:
Dejenere Durum: \(y_1 = 0\) ve Mertebe-2 Noktaları
Katlama formüllerinde paydanın \(2y_1\) olduğunu gördük. Eğer \(y_1 = 0\) ise bu payda sıfırlanır ve formül çöker — daha doğrusu, teğet eğimi sonsuz (\(\infty\)) olur, yani teğet doğrusu tam olarak diktir.
Bir noktanın y-koordinatının sıfır olması, o noktanın tam olarak x-ekseninde yattığı anlamına gelir. Eğrinin x-eksenine göre ayna simetrisi olduğunu hatırlayın: her \((x, y)\) noktası için \((x, -y)\) de eğri üzerindedir. \(y = 0\) olduğunda \(-y = 0\) da sıfırdır, yani nokta kendi negatifi ile özdeştir: \(-P = P\). Bu çarpıcı özellik ne anlama gelir?
Bir noktanın kendisi ile toplandığında sonsuz noktayı vermesi, grubun mertebe-2 (order-2) bir eleman içerdiği anlamına gelir. Bu noktalar eliptik eğrinin 2-torsion noktaları olarak adlandırılır. Kriptografik eğrilerde bu tür noktalar dikkatle yönetilir: güvenli bir üretici nokta \(G\) seçilirken \(2G \neq \mathcal{O}\) koşulu, yani \(G\)'nin mertebesi 2'den büyük olmalı koşulu zorunlu tutulur.
Pratikte secp256k1 ve P-256 gibi kriptografik eğrilerde tanımlanan üretici noktanın mertebesi, göresel asal (prime order) bir gruptur — yani bu grubun hiçbir elemanı mertebe-2 değildir. Bu tasarım kararı, hem katlama formülünün her zaman güvenle uygulanabilmesini sağlar, hem de grubun yapısını kriptografik saldırılara karşı dayanıklı kılar.
Nokta Toplama ile Katlama: Farklar ve Bağlantılar
| Kriter | Nokta Toplama (\(P + Q\)) | Nokta Katlama (\(2P\)) |
|---|---|---|
| Koşul | \(P \neq Q\), yani \(x_1 \neq x_2\) | \(P = Q\), yani aynı nokta kendisiyle |
| Geometrik Araç | İki noktadan geçen kesen doğru (secant line) | P noktasında eğriye çizilen teğet doğrusu |
| Eğim Formülü | \(m = (y_2 - y_1)(x_2 - x_1)^{-1} \bmod p\) | \(m = (3x_1^2 + a)(2y_1)^{-1} \bmod p\) |
| \(x_3\) Formülü | \(m^2 - x_1 - x_2 \bmod p\) | \(m^2 - 2x_1 \bmod p\) |
| \(y_3\) Formülü | \(m(x_1 - x_3) - y_1 \bmod p\) — her iki durumda aynı | |
| Dejenere Durum | \(x_1 = x_2\) ve \(y_1 = -y_2\) → \(\mathcal{O}\) | \(y_1 = 0\) → \(\mathcal{O}\) (mertebe-2) |
| Kriptografik Kullanım | Genel grup işlemi, protokol seviyesi | Skaler çarpım döngüsünün her adımı (\(kG\) hesabı) |
Adım Adım Sayısal Örnek: \(\mathbb{F}_{97}\) Üzerinde \(2P\)
Aynı eğri ve alan üzerinde devam edelim: \(y^2 = x^3 + 7\) (\(a = 0, b = 7\)), \(p = 97\). Başlangıç noktamız olarak \(P = (3, 6)\) seçelim — \(a = 0\) olduğu için katlama formülü özellikle temizleşir.
Katlama ile Skaler Çarpım: Double-and-Add Bağlantısı
Nokta katlamanın kriptografik açıdan neden bu kadar stratejik olduğunu anlamak için bir adım geriye çekilelim. ECC'de merkezi işlem, gizli anahtar \(k\) ve üretici nokta \(G\) ile gerçekleştirilen skaler çarpımdır: \(Q = k \cdot G\). Bu işlem matematiksel olarak \(G\)'nin \(k\) kez kendisiyle toplanmasıdır: \(G + G + G + \ldots + G\).
Eğer bunu naif bir döngüyle yapacak olsaydık — yani \(k\) adım \(G\) ekleseydik — 256 bitlik bir anahtar için \(2^{256}\) operasyon gerekirdi; evrenin ömründen uzun bir süre. Bunun yerine kullanılan Double-and-Add (İkile ve Topla) algoritması, ikili sayma sistemini kullanarak bu hesabı \(O(\log_2 k)\) operasyona, yani yaklaşık 256 adıma indirir.
Algoritmanın iki temel operasyonu vardır: Nokta toplama ve nokta katlama. Her bit için:
| Bit Değeri | Yapılan İşlem | İçerik |
|---|---|---|
| Her bit | Double (Katla) | Mevcut sonucu 2 ile katla: \(R \leftarrow 2R\). Her zaman yapılır. |
| Bit = 1 | Add (Topla) | Üretici noktayı ekle: \(R \leftarrow R + G\). Yalnızca bit 1 olduğunda. |
| Bit = 0 | Sadece Double | Ekleme atlanır, yalnızca katlama yapılır. |
Örnek olarak \(k = 13\) için:\(13 = 1101_2\) (binary). Algoritma şöyle çalışır:
256 bit için bu, 256 katlama + en fazla 256 toplama işlemi demektir. Toplamda \(\sim\)512 operasyon, birkaç milisaniye. Ve bu hesabın tersini yapmak — yani sadece \(G\) ve \(Q = kG\) bilindiğinde \(k\)'yı bulmak — hâlâ kriptografik açıdan imkânsız kabul edilmektedir.
Double-and-Add algoritmasında bit değerine bağlı olarak farklı işlemler yapılması (bit 0 → sadece double, bit 1 → double + add) bir güvenlik zafiyeti yaratır: saldırganlar CPU zaman ölçümü, güç tüketimi veya elektromanyetik yayılım analizi yoluyla hangi bitlerin 0, hangilerinin 1 olduğunu tespit edebilir ve gizli anahtarı elde edebilir. Bu timing/power side-channel attack olarak bilinir. Gerçek kütüphanelerde bunun yerine her bit için eşit sayıda işlem yapan constant-time Montgomery Ladder algoritması kullanılır.
Rust ile Nokta Katlama Implementasyonu
Aşağıdaki Rust kodu, nokta katlama operasyonunu point-addition.rs'teki altyapı üzerine genişletir. Aynı EllipticCurve struct'ı kullanılır; double() metodu ayrı olarak implemente edilmiş, scalar_mul() ise Double-and-Add algoritmasını uçtan uca çalıştırır. Sabit zamanlı bir conditional_select primitive'i de eklenmiştir.
/// point_doubling.rs — EllipticCurve'e double() ve scalar_mul() ekleniyor.
/// point_addition.rs'teki Point enum ve EllipticCurve struct üzerine inşa edilir.
use crate::{EllipticCurve, Point};
impl EllipticCurve {
/// Bir noktayı kendisiyle toplar: 2P.
/// Dejenere durum: y₁ = 0 ise teğet dik, 2P = Infinity döner.
pub fn double(&self, p: Point) -> Point {
match p {
// 2 * O = O (birim eleman sabit kalır)
Point::Infinity => Point::Infinity,
Point::Coordinate(x1, y1) => {
// Dejenere: y₁ = 0 → teğet dik → 2P = O
if y1 == 0 {
return Point::Infinity;
}
// m = (3x₁² + a) · (2y₁)⁻¹ mod p
let numerator = (3 * x1 % self.p
* x1 % self.p
+ self.a) % self.p;
let denominator = self.mod_inv(2 * y1 % self.p);
let m = numerator * denominator % self.p;
// x₃ = m² - 2x₁ mod p
let x3 = (m * m % self.p
+ 2 * self.p // negatif taşmayı önle
- 2 * x1 % self.p) % self.p;
// y₃ = m(x₁ - x₃) - y₁ mod p
let y3 = (m * ((x1 + self.p - x3) % self.p) % self.p
+ self.p - y1) % self.p;
Point::Coordinate(x3, y3)
}
}
}
/// Double-and-Add ile k·P skaler çarpımını hesaplar.
/// Karmaşıklık: O(log₂ k) — 256-bit k için ~512 adım.
pub fn scalar_mul(&self, mut k: u128, point: Point) -> Point {
let mut result = Point::Infinity; // Birim eleman ile başla
let mut addend = point; // Katlanacak baz nokta
while k > 0 {
// En düşük bit 1 ise mevcut addend'i ekle
if k & 1 == 1 {
result = self.add(result, addend);
}
// Her adımda baz noktayı katla: G → 2G → 4G → 8G …
addend = self.double(addend);
k >>= 1; // bir sonraki bite geç
}
result
}
}
fn main() {
// y² = x³ + 7 (mod 97) üzerinde P = (3, 6)
let curve = EllipticCurve { a: 0, p: 97 };
let p = Point::Coordinate(3, 6);
// Nokta katlama: 2P
let doubled = curve.double(p);
println!("2P = {:?}", doubled); // Beklenen: (90, 65)
// Skaler çarpım: 13 * P
let k13 = curve.scalar_mul(13, p);
println!("13P = {:?}", k13);
// Grup mertebesi doğrulaması: eğer n * G = O ise n mertebedir
// (küçük örnek için n deneme yanılma ile bulunabilir)
let p_doubled_again = curve.double(
Point::Coordinate(0, 0) // y = 0 → dejenere test
);
assert_eq!(p_doubled_again, Point::Infinity, "y=0 → 2P = O olmalı");
println!("Dejenere durum doğrulandı: y=0 → 2P = O");
}