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