diff --git a/Cargo.toml b/Cargo.toml index 87d78ae1..dff91924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license ="MIT OR Apache-2.0" name ="ronkathon" repository ="https://github.com/pluto/ronkathon" version ="0.1.0" -exclude =["CHANGELOG.md", "src/tree/ConstructMerkleTree.gif"] +exclude =["CHANGELOG.md", "src/tree/ConstructMerkleTree.gif", "src/dsa/keygen.gif", "src/dsa/sign_and_verify.gif"] [dependencies] rand ="0.8" @@ -14,7 +14,6 @@ itertools ="0.13" hex ="0.4" crypto-bigint ="0.6.0-rc.6" sha2 ="0.10" -hex-literal ="0.4" [dev-dependencies] rstest ="0.23" @@ -23,6 +22,7 @@ ark-ff ={ version="0.5", features=["std"] } ark-crypto-primitives={ version="0.5", features=["sponge"] } des ="0.8" chacha20 ="0.9" +hex-literal ="0.4" [[bin]] name="hmac_sha256_bin" diff --git a/src/dsa/README.md b/src/dsa/README.md new file mode 100644 index 00000000..cdf752a3 --- /dev/null +++ b/src/dsa/README.md @@ -0,0 +1,45 @@ +# Digital Signature Algorithms (DSA) + +## Introduction + +**What are digital signatures?** + +Just like its name, **Digital Signatures** are digital analogs of physical signatures. For example, when you want to write a cheque you have to "sign" it for authentication purposes. But think about how you would do the same over the internet. +Here is where **Digital Signatures** come into the picture. Just like physical signatures, digital signatures provide *authenticity*. Like how physical signatures on a cheque provide +a way to "verify" the identity of a signer. +Digital signatures also provide integrity. That is, they provide a mechanism to detect unauthorized modification. +Digital signatures also have another nice property: non-repudiation. Once a signer signs a message or a document, they cannot deny having done so. + +In conclusion, **Digital Signatures** have the following properties: +1. Integrity +2. Authenticity +3. Non-repudiation + +**How does a digital signature scheme look like?** + +Digital signature schemes consists of three algorithms **Gen**, **Sign**, **Verify**, such that: + +1. The key generation algorithm, $Gen$ which takes in the security parameter $1^n$ and outputs public key, $pk$ and private key, $sk$. +2. The signing algorithm $Sign$ takes as input the keys and a message and outputs a signature. +3. The verification algorithm $Verify$, takes as input the public key, a message, and a signature. +It outputs bit 1 if the signature is valid for the given message and public key, otherwise 0. + +**How is a digital signature scheme used?** + +To explain how digital signature schemes are used, let's take the example of two people, Bobby and Alex. +Bobby is the one whose signature is required, so Bobby will run the $Gen(1^n)$ algorithm to obtain, $pk, sk$. +Then, the public key, $pk$, is publicized as belonging to Bobby. This way it can be verified that $pk$ actually belongs to Bobby. This one of the critical parts of a secure digital signature scheme. +You can read more on this here: [Public key infrastructure](https://en.wikipedia.org/wiki/Public_key_infrastructure) + +![](./keygen.gif) + +Now when Alex sends a message(document, contract, etc.), $m$, for Bobby to sign, they compute the signature, $s$ as, $s\leftarrow Sign(sk,m)$ and sents $s$ to Alex or any other party who wants to take a look. +Now, any party who wants to see if Bobby signed the document or not, applies the verification algorithm using the public key as $Verify(pk,m,s)$. + +![](./sign_and_verify.gif) + +## References + +1. "Introduction to Modern Cryptography" by Jonathan Katz and Yehuda Lindell + + diff --git a/src/dsa/eddsa/curve.rs b/src/dsa/eddsa/curve.rs index 0a5a415e..808250d6 100644 --- a/src/dsa/eddsa/curve.rs +++ b/src/dsa/eddsa/curve.rs @@ -1,9 +1,9 @@ //! Contains code related the Ed25519 curve and field as given in [RFC8032] //! //! References (with abbreviation used in the code) -//! [RFC8032] "Edwards-Curve Digital Signature Algorithm (EdDSA)" -//! [EdwardsRevisited] "Twisted Edwards Curves Revisited", Hisil, H., Wong, K., Carter, G., and E. -//! Dawson +//! 1. [RFC8032] "Edwards-Curve Digital Signature Algorithm (EdDSA)" +//! 2. [EdwardsRevisited] "Twisted Edwards Curves Revisited", Hisil, H., Wong, K., Carter, G., +//! and E. Dawson use std::ops::{Add, AddAssign, Mul}; use crypto_bigint::{ @@ -12,18 +12,18 @@ use crypto_bigint::{ Encoding, U256, U512, }; -// The prime number defining the base field +// `P`: Prime number defining the base field impl_modulus!(P, U256, "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"); -/// The prime `P` minus 2. This is used for calculation of the inverse. +/// `P` minus 2. Used for calculation of the inverse. pub const P_2: U256 = U256::from_be_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb"); -/// The order of subgroup. +/// Subgroup order for the Ed25519 curve. pub const ORDER: U256 = U256::from_be_hex("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"); -// The modulus used by `ScalarField64` +// Modulus used by `ScalarField64` impl_modulus!( L64, U512, @@ -31,32 +31,33 @@ impl_modulus!( 1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed" ); -// The modulus used by 'ScalarField' +// Modulus used by 'ScalarField' impl_modulus!(L, U256, "1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"); -/// Type of a 256-bit element of Ed25519 curve's base field +/// Type representing a 256-bit element in the Ed25519 curve's base field. pub type BaseField = ConstMontyForm; -/// Type of a 512-bit element of Ed25519 curve's scalar field +/// Type representing a 512-bit element in the Ed25519's scalar field. pub type ScalarField64 = ConstMontyForm; -/// Type of a 256-bit element of Ed25519 curve's scalar field +/// Type representing a 256-bit element in the Ed25519 curve's scalar field. pub type ScalarField = ConstMontyForm; -/// '0' of type `BaseField` +/// Constant representing zero in the base field. pub const BF_ZERO: BaseField = BaseField::new(&U256::ZERO); -/// '1' of type `BaseField` +/// Constant representing one in the base field. pub const BF_ONE: BaseField = BaseField::new(&U256::ONE); -/// '2' of type `BaseField` -pub const BF_TWO: BaseField = ConstMontyForm::add(&BF_ONE, &BF_ONE); +/// Constant representing one in the base field. +pub const BF_TWO: BaseField = BaseField::new(&U256::from_u8(2u8)); +/// Constant 'd' used in curve operations (refer to [RFC8032] for details). const D: BaseField = BaseField::new(&U256::from_be_hex( "52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3", )); -/// '0' of type `ScalarField` +/// Constant representing zero in the `ScalarField` type. pub const SF_ZERO: ScalarField = ScalarField::new(&U256::ZERO); -/// '0' of type `ScalarField64` +/// Constant representing zero in the `ScalarField64` type. pub const SF_ZERO64: ScalarField64 = ScalarField64::new(&U512::ZERO); -/// '1' of type `ScalarField` +/// Constant representing one in the `ScalarField` type. pub const SF_ONE: ScalarField = ScalarField::new(&U256::ONE); /// Represents a point on the Ed25519 curve in extended homogeneous coordinates. @@ -70,7 +71,8 @@ pub struct Coordinate { /// The additive identity of the Ed25519 curve group. pub const IDENTITY: Coordinate = Coordinate::new(BF_ZERO, BF_ONE); -/// The point on the Ed25519 curve used as a generator or base point. + +/// The point on the Ed25519 curve used as a generator or base point as defined in [RFC8032] pub const GENERATOR: Coordinate = Coordinate::new( BaseField::new(&U256::from_be_hex( "216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a", @@ -82,44 +84,51 @@ pub const GENERATOR: Coordinate = Coordinate::new( #[inline] fn get_sign_bit(x: &BaseField) -> u8 { x.retrieve().to_le_bytes()[0] & 1 } + +/// Inverse of a element in the BaseField. +/// Calculated as: x^(-1) = x^(P-2) (mod P), where P is modulus of the field. #[inline] fn inv(x: BaseField) -> BaseField { x.pow(&P_2) } /// Find the square root of an element of the `BaseField` /// It uses the algorithm given in Section 5.1.1 of [RFC8032] utilizing the special case of /// `P = 5 (mod 8)`. To read more, see: (https://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus) -pub fn sqrt(val: &BaseField) -> Option { +pub fn sqrt(x: &BaseField) -> Option { const TWO: U256 = U256::from_u8(2u8); const THREE: U256 = U256::from_u8(3u8); let mut exp = (P::MODULUS.get() + THREE).shr(3); - let y = val.pow(&exp); + let y1 = x.pow(&exp); - if y * y == *val { - return Some(y); + if y1 * y1 == *x { + return Some(y1); } exp = (P::MODULUS.get() - U256::ONE).shr(2); let z = BaseField::new(&TWO).pow(&exp); - let x = y * z; - if x * x == *val { - return Some(x); + let y2 = y1 * z; + if y2 * y2 == *x { + return Some(y2); } else { return None; } } impl Coordinate { - /// Create an instance of the `Coordinate` given the affine coordinates of a point on the Ed25519 - /// curve. + /// Creates a new `Coordinate` point on the Ed25519 curve from its affine coordinates (x, y). + + /// **Note:** This constructor assumes the provided `x` and `y` coordinates represent a valid + /// point on the Ed25519 curve. pub const fn new(x: BaseField, y: BaseField) -> Self { let z = BF_ONE; let t = ConstMontyForm::mul(&x, &y); Self { x, y, t, z } } - /// Double the `Coordinate` using the equations for the extended homogeneous coordinates which - /// avoid the expensive field inversion operations given in the section 3.3 of [EdwardsRevisited] + /// Doubles the current `Coordinate` point. + /// + /// This method uses the efficient extended homogeneous coordinates doubling formula from + /// Section 3.3 of [EdwardsRevisited] to avoid expensive field inversion. pub fn double(&self) -> Self { let a = self.x.square(); let b = self.y.square(); @@ -138,40 +147,51 @@ impl Coordinate { Self { x, y, t, z } } - /// Encode a `Coordinate` on the curve for a compact representation. + /// Encodes the `Coordinate` point into a compact 32-byte representation. + /// + /// This encoding scheme represents the y-coordinate directly and encodes the x-coordinate's + /// sign in the highest bit of the last byte. pub fn encode(&self) -> [u8; 32] { let x = self.x * inv(self.z); let y = self.y * inv(self.z); + let mut s = y.retrieve().to_le_bytes(); - if get_sign_bit(&x) == 1u8 { - s[31] |= 1 << 7; - } + + // Set the highest bit the y to the sign of x. + s[31] |= get_sign_bit(&x) << 7; s } - /// Decode a `Coordinate` on the curve which is encoded using the `encode` method. + /// Decodes a `Coordinate` point from its compact 32-byte representation. + /// + /// Returns `None` if the decoding process fails. pub fn decode(mut bytes: [u8; 32]) -> Option { let xsign = bytes[31] >> 7; bytes[31] &= !(1 << 7); let raw_y = U256::from_le_bytes(bytes); + // Check if raw_y is valid. if raw_y >= P::MODULUS.get() { return None; } let y = BaseField::new(&raw_y); + // Find x^2, given the value of y on the curve. let y2 = y * y; let x2 = (y2 - BF_ONE) * inv(D * y2 + BF_ONE); + // Find the square root of x2 if there is one, other return None let mut x = match sqrt(&x2) { Some(x) => x, None => return None, }; + // There is only one correct value of sign of '0' in the basefield. if x == BF_ZERO && get_sign_bit(&x) != xsign { return None; } + // Correct the sign of x. if get_sign_bit(&x) != xsign { x = -x; } @@ -181,6 +201,7 @@ impl Coordinate { } impl PartialEq for Coordinate { + /// Checks for equality of two `Coordinate` points on the Ed25519 curve. fn eq(&self, other: &Self) -> bool { // Convert the extended homogeneous coordinates to affine coordinates before comparison. let x1 = self.x * inv(self.z); @@ -196,8 +217,10 @@ impl PartialEq for Coordinate { impl Add for Coordinate { type Output = Self; - /// Add two `Coordinate`s, using the equations for the extended homogeneous coordinates which - /// avoid the expensive field inversion operation given the section 3.2 of [EdwardsRevisited] + /// Adds two `Coordinate` points on the Ed25519 curve. + /// + /// This implementation uses the efficient extended homogeneous coordinates addition formula + /// from Section 3.2 of [EdwardsRevisited] to avoid expensive field inversions. fn add(self, rhs: Self) -> Self::Output { let a = (self.y - self.x) * (rhs.y - rhs.x); let b = (self.y + self.x) * (rhs.y + rhs.x); @@ -218,13 +241,16 @@ impl Add for Coordinate { } impl AddAssign for Coordinate { + /// Adds another `Coordinate` point to the current point in-place. fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; } } impl Mul for Coordinate { type Output = Self; - /// Multiply a `Coordinate` with a `ScalarField`. + /// Performs scalar multiplication on a `Coordinate` point. + /// + /// This implementation uses the double-and-add algorithm for efficient scalar multiplication. fn mul(self, rhs: ScalarField) -> Self::Output { if rhs == SF_ZERO { return IDENTITY; diff --git a/src/dsa/eddsa/mod.rs b/src/dsa/eddsa/mod.rs index a1287222..fc33ecf6 100644 --- a/src/dsa/eddsa/mod.rs +++ b/src/dsa/eddsa/mod.rs @@ -1,4 +1,7 @@ //! Edwards-curve Digital Signature Algorithm (EdDSA) based on the RFC 8032. +//! +//! References: +//! 1. [RFC8032] "Edwards-Curve Digital Signature Algorithm (EdDSA)". use crypto_bigint::{Encoding, U256, U512}; use curve::{Coordinate, ScalarField, ScalarField64, GENERATOR, ORDER}; use sha2::{Digest, Sha512}; @@ -7,6 +10,7 @@ pub mod curve; #[cfg(test)] mod tests; +/// Calculates the 64-byte SHA-512 hash. fn sha512_hash(bytes: &[u8]) -> [u8; 64] { let mut hasher = Sha512::new(); hasher.update(bytes); @@ -16,15 +20,22 @@ fn sha512_hash(bytes: &[u8]) -> [u8; 64] { } fn clamp(mut bytes: [u8; 32]) -> [u8; 32] { + // Set the first three bits to zero. for i in 0..3 { bytes[0] &= !(1 << i); } + // Set the last second bit to one. bytes[31] |= 1 << 6; + // Set the last bit to zero. bytes[31] &= !(1 << 7); bytes } +/// Calculates `x (mod L)`, where L(see `curve.rs`) is the order of the curve subgroup. +/// Returns it result as a 32-byte array. fn reduce_by_order(x: [u8; 64]) -> [u8; 32] { + // ScalarField64 calculates the modulus of 64-byte/512-bit number with `L`(the order of the + // subgroup) let r1 = ScalarField64::new(&U512::from_le_bytes(x)).retrieve().to_le_bytes(); let mut r2 = [0u8; 32]; r2.copy_from_slice(&r1[..32]); @@ -35,9 +46,10 @@ fn reduce_by_order(x: [u8; 64]) -> [u8; 32] { pub struct Ed25519; impl Ed25519 { - /// Generate the `public_key` given the `private_key`. - /// Both keys are of size 32 bytes. - /// Returns both the keys as (private_key, public_key) tuple. + /// Generates the `public_key` given the `private_key`. + /// + /// The `private_key` should be a randomly generated 32-byte array. + /// Returns both the keys as a (private_key, public_key) tuple. pub fn keygen(private_key: [u8; 32]) -> ([u8; 32], [u8; 32]) { let keyhash = sha512_hash(&private_key); let mut h = [0u8; 32]; @@ -48,30 +60,44 @@ impl Ed25519 { (private_key, public_key.encode()) } - /// Sign the `msg` using the `public_key` and `private_key`. - /// Returns the signature of size 64 bytes. - pub fn sign(private_key: [u8; 32], public_key: [u8; 32], msg: &[u8]) -> [u8; 64] { + /// Sign the `message` using the `public_key` and `private_key`. + /// + /// It uses the algorithm given in Section 5.1.6 of [RFC8032], which is as follows: + /// Notation: H <- SHA-512 hash function, + /// Enc <- Encoding function for a point on curve. See `curve.rs` + /// + /// 1. `h = H(private_key)`, 64-byte hash of private_key + /// 2. Split `h` into two 32-byte halves, `s` and `prefix`. `s` is used as a scalar. + /// 3. r = H(prefix | message), hash of prefix concatenated with the message. `r` is used as a + /// scalar. + /// 4. R = Enc(r * B), the encoding of scalar multiplication of `B`, the generator the curve + /// group, with `r`, the scalar from the previous step. + /// 5. k = H(R | public_key | message), k is used as a scalar. + /// 6. S = r + k * s, where addition and multiplication are of the scalar field. + /// + /// The 32-byte R and S are concatenated to form the 64-byte signature. + pub fn sign(private_key: [u8; 32], public_key: [u8; 32], message: &[u8]) -> [u8; 64] { let keyhash = sha512_hash(&private_key); - let mut a1: [u8; 32] = [0u8; 32]; - a1.copy_from_slice(&keyhash[..32]); - let a = ScalarField::new(&U256::from_le_bytes(clamp(a1))); + let mut s1: [u8; 32] = [0u8; 32]; + s1.copy_from_slice(&keyhash[..32]); + let s = ScalarField::new(&U256::from_le_bytes(clamp(s1))); - let mut seed: [u8; 32] = [0u8; 32]; - seed.copy_from_slice(&keyhash[32..]); + let mut prefix: [u8; 32] = [0u8; 32]; + prefix.copy_from_slice(&keyhash[32..]); - let r1 = [&seed, msg].concat(); + let r1 = [&prefix, message].concat(); let r2 = sha512_hash(&r1); let r3 = reduce_by_order(r2); let r = ScalarField::new(&U256::from_le_bytes(r3)); let big_r = (GENERATOR * r).encode(); - let h1 = [&big_r, &public_key, msg].concat(); - let h2 = sha512_hash(&h1); - let h = ScalarField::new(&U256::from_le_bytes(reduce_by_order(h2))); + let k1 = [&big_r, &public_key, message].concat(); + let k2 = sha512_hash(&k1); + let k = ScalarField::new(&U256::from_le_bytes(reduce_by_order(k2))); - let s1 = r + h * a; + let s1 = r + k * s; let big_s = s1.retrieve().to_le_bytes(); let mut output = [0u8; 64]; @@ -81,37 +107,51 @@ impl Ed25519 { output } - /// Verifies message and signature pair given a `public_key`. + /// Verify the `signature` on a `message` using the `public_key` + /// + /// It uses the algorithm given in Section 5.1.7 of [RFC8032], which is as follows: + /// Notation: H <- SHA-512 hash function, + /// Decode <- Decoding function for a point on curve. See `curve.rs` + /// + /// 1. Split the signature into two 32-byte halves, R and S. R is decoded into a point on the + /// curve. S is used as a scalar. + /// 2. A = Decode(public_key), decode the public_key to a point on the curve. + /// 3. k = H(R | public_key | message), hash of first 32-byte of signature concatenated with + /// public_key and the message. + /// 4. Check if 8 * S * B == 8*(R + k*A). pub fn verify(public_key: [u8; 32], message: &[u8], signature: [u8; 64]) -> bool { - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); + let mut big_r = [0u8; 32]; + let mut big_s = [0u8; 32]; + big_r.copy_from_slice(&signature[..32]); + big_s.copy_from_slice(&signature[32..]); - let r1 = match Coordinate::decode(r) { + // Decode `big_r` into a point + let r = match Coordinate::decode(big_r) { Some(x) => x, None => return false, }; - let s_uint = U256::from_le_bytes(s); + // Represent `big_s` into a `ScalarField` element. + let s_uint = U256::from_le_bytes(big_s); if s_uint >= ORDER { return false; } - let s = ScalarField::new(&s_uint); + // Decode the public_key as a point. let a = match Coordinate::decode(public_key) { Some(x) => x, None => return false, }; - let h1 = [&r, &public_key, message].concat(); - let h2 = sha512_hash(&h1); - let h = ScalarField::new(&U256::from_le_bytes(reduce_by_order(h2))); + let k1 = [&big_r, &public_key, message].concat(); + let k2 = sha512_hash(&k1); + let k = ScalarField::new(&U256::from_le_bytes(reduce_by_order(k2))); - let mut rhs = r1 + a * h; + let mut rhs = r + a * k; let mut lhs = GENERATOR * s; + // Calculates 8*lhs and 8*rhs for _ in 0..3 { lhs = lhs.double(); rhs = rhs.double(); diff --git a/src/dsa/eddsa/tests.rs b/src/dsa/eddsa/tests.rs index 062bdee8..433342c4 100644 --- a/src/dsa/eddsa/tests.rs +++ b/src/dsa/eddsa/tests.rs @@ -6,6 +6,8 @@ use rstest::*; use super::*; +/// Test the `Ed25519` digital signature scheme using the test vectors given in Section 7.1 of RFC +/// 8032. #[rstest] #[case( hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"), @@ -56,8 +58,9 @@ pub fn decode_hex(s: &str) -> Result, ParseIntError> { (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16)).collect() } +/// Test against the 1024 test vectors given in the ["Ed25519 test vectors", Bernstein et al. +/// 2011] (http://ed25519.cr.yp.to/python/sign.input) #[test] -// #[ignore] fn test_large() { let file = include_str!("./test.input"); let lines: Vec<&str> = file.split('\n').collect(); diff --git a/src/dsa/keygen.gif b/src/dsa/keygen.gif new file mode 100644 index 00000000..8c6f5277 Binary files /dev/null and b/src/dsa/keygen.gif differ diff --git a/src/dsa/mod.rs b/src/dsa/mod.rs index 93ab4708..9126f764 100644 --- a/src/dsa/mod.rs +++ b/src/dsa/mod.rs @@ -1,4 +1,5 @@ //! Digital Signatures +#![doc = include_str!("./README.md")] pub mod ecdsa; pub mod eddsa; diff --git a/src/dsa/sign_and_verify.gif b/src/dsa/sign_and_verify.gif new file mode 100644 index 00000000..1ed1b014 Binary files /dev/null and b/src/dsa/sign_and_verify.gif differ