diff --git a/Cargo.lock b/Cargo.lock index 6fa7f63..fb16f14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,19 +2,108 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "ecc-rust" version = "0.1.0" dependencies = [ + "env_logger", + "log", "num-bigint", + "rand", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "num-bigint" version = "0.4.6" @@ -23,6 +112,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "rand", ] [[package]] @@ -42,3 +132,233 @@ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index c63cff2..5418445 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,7 @@ version = "0.1.0" edition = "2021" [dependencies] -num-bigint = "0.4.6" +num-bigint = { version = "0.4", features = ["rand"] } +rand = "0.8" +log = "0.4" +env_logger = "0.10" \ No newline at end of file diff --git a/src/ecdsa.rs b/src/ecdsa.rs new file mode 100644 index 0000000..b50a0fa --- /dev/null +++ b/src/ecdsa.rs @@ -0,0 +1,222 @@ +use crate::{EllipticCurve, FiniteField, Point}; +use log::{debug, info, warn}; +use num_bigint::{BigUint, RandBigInt}; +use rand::thread_rng; + +pub struct ECDSA { + curve: EllipticCurve, + generator: Point, + order: BigUint, +} + +impl ECDSA { + pub fn new(curve: EllipticCurve, generator: Point, order: BigUint) -> Self { + debug!("Creating new ECDSA instance"); + assert!( + curve.is_on_curve(&generator), + "Generator point must be on the curve" + ); + ECDSA { + curve, + generator, + order, + } + } + + pub fn generate_keypair(&self) -> (BigUint, Point) { + debug!("Generating new keypair"); + let private_key = self.generate_random_private_key(); + let public_key = self.generate_public_key(&private_key); + + self.validate_public_key(&public_key); + info!("Keypair generated successfully"); + (private_key, public_key) + } + + pub fn generate_public_key(&self, private_key: &BigUint) -> Point { + debug!("Generating public key from private key"); + self.curve.mul(&self.generator, private_key) + } + + pub fn sign(&self, message: &BigUint, private_key: &BigUint) -> Result<(BigUint, BigUint), &'static str> { + self.validate_input(message, private_key)?; + debug!("Signing message"); + let k = self.generate_random_private_key(); + self.sign_with_k(message, private_key, &k) + } + + pub fn verify( + &self, + message: &BigUint, + signature: &(BigUint, BigUint), + public_key: &Point, + ) -> bool { + debug!("Verifying signature"); + let (r, s) = signature; + + if !self.is_valid_signature(r, s) { + return false; + } + + let s_inv = FiniteField::inv_mul(s, &self.order); + let u1 = FiniteField::mul(message, &s_inv, &self.order); + let u2 = FiniteField::mul(r, &s_inv, &self.order); + let point = self.calculate_verification_point(&u1, &u2, public_key); + + self.is_signature_valid(point, r) + } + + fn validate_input(&self, message: &BigUint, private_key: &BigUint) -> Result<(), &'static str> { + if private_key >= &self.order { + return Err("Private key must be less than the order of the curve"); + } + if message >= &self.order { + return Err("Message must be less than the order of the curve"); + } + Ok(()) + } + + fn sign_with_k( + &self, + message: &BigUint, + private_key: &BigUint, + k: &BigUint, + ) -> Result<(BigUint, BigUint), &'static str> { + if k >= &self.order { + return Err("k must be less than the order of the curve"); + } + + let r = self.calculate_r(k); + let s = self.calculate_s(message, private_key, k, &r); + + info!("Message signed successfully"); + Ok((r, s)) + } + + fn calculate_r(&self, k: &BigUint) -> BigUint { + match self.curve.mul(&self.generator, k) { + Point::Coordinates(x, _) => x, + Point::Identity => { + warn!("Unexpected point at infinity during signing"); + panic!("Unexpected point at infinity"); + } + } + } + + fn calculate_s( + &self, + message: &BigUint, + private_key: &BigUint, + k: &BigUint, + r: &BigUint, + ) -> BigUint { + let s = FiniteField::mul(r, private_key, &self.order); + let s = FiniteField::add(message, &s, &self.order); + let k_inv = FiniteField::inv_mul(k, &self.order); + FiniteField::mul(&s, &k_inv, &self.order) + } + + fn is_valid_signature(&self, r: &BigUint, s: &BigUint) -> bool { + if r >= &self.order || s >= &self.order { + warn!("Invalid signature: r or s is too large"); + return false; + } + true + } + + fn calculate_verification_point( + &self, + u1: &BigUint, + u2: &BigUint, + public_key: &Point, + ) -> Point { + let u1a = self.curve.mul(&self.generator, u1); + let u2b = self.curve.mul(public_key, u2); + self.curve.add(&u1a, &u2b) + } + + fn is_signature_valid(&self, point: Point, r: &BigUint) -> bool { + match point { + Point::Coordinates(x, _) => x == *r, + Point::Identity => { + warn!("Unexpected point at infinity during verification"); + false + } + } + } + + fn generate_random_private_key(&self) -> BigUint { + debug!("Generating random private key"); + thread_rng().gen_biguint_range(&BigUint::from(1u32), &self.order) + } + + fn validate_public_key(&self, public_key: &Point) { + assert!( + self.curve.is_on_curve(public_key), + "Generated public key is not on the curve" + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use env_logger; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + fn create_test_ecdsa() -> ECDSA { + let curve = EllipticCurve { + a: BigUint::from(2u32), + b: BigUint::from(2u32), + p: BigUint::from(17u32), + }; + + ECDSA::new( + curve, + Point::Coordinates(BigUint::from(5u32), BigUint::from(1u32)), + BigUint::from(19u32), + ) + } + + #[test] + fn test_generate_keypair() { + init(); + let ecdsa = create_test_ecdsa(); + let (private_key, public_key) = ecdsa.generate_keypair(); + assert!(private_key < ecdsa.order); + assert!( + ecdsa.curve.is_on_curve(&public_key), + "Generated public key is not on the curve" + ); + } + + #[test] + fn test_sign_and_verify() { + init(); + let ecdsa = create_test_ecdsa(); + let (private_key, public_key) = ( + BigUint::from(7u32), + ecdsa.generate_public_key(&BigUint::from(7u32)), + ); + let message = BigUint::from(10u32); + let signature = ecdsa.sign_with_k(&message, &private_key, &BigUint::from(18u32)).unwrap(); + assert!(ecdsa.verify(&message, &signature, &public_key)); + } + + #[test] + fn test_verify_invalid_signature() { + init(); + let ecdsa = create_test_ecdsa(); + let (private_key, public_key) = ( + BigUint::from(7u32), + ecdsa.generate_public_key(&BigUint::from(7u32)), + ); + let message = BigUint::from(5u32); + let mut signature = ecdsa.sign(&message, &private_key).unwrap(); + signature.1 += BigUint::from(1u32); // Modify the signature to make it invalid + assert!(!ecdsa.verify(&message, &signature, &public_key)); + } +} diff --git a/src/lib.rs b/src/lib.rs index 025092b..0f7ad6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,11 @@ mod curves; mod ec; mod ff; mod point; +mod ecdsa; pub use curves::*; pub use ec::*; pub use ff::*; pub use point::*; +pub use ecdsa::*;