use crate::Hasher;
use crate::hash::sha512::Sha512;
#[derive(Clone, Copy, Debug)]
struct Fe25519([u64; 4]);
const P: [u64; 4] = [
0xFFFF_FFFF_FFFF_FFED,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0x7FFF_FFFF_FFFF_FFFF,
];
impl Fe25519 {
const ZERO: Fe25519 = Fe25519([0, 0, 0, 0]);
const ONE: Fe25519 = Fe25519([1, 0, 0, 0]);
fn reduce(mut limbs: [u64; 4]) -> Self {
let (r0, borrow) = limbs[0].overflowing_sub(P[0]);
let (r1, borrow) = sbb(limbs[1], P[1], borrow);
let (r2, borrow) = sbb(limbs[2], P[2], borrow);
let (r3, borrow) = sbb(limbs[3], P[3], borrow);
let mask = if borrow { u64::MAX } else { 0 };
limbs[0] = (limbs[0] & mask) | (r0 & !mask);
limbs[1] = (limbs[1] & mask) | (r1 & !mask);
limbs[2] = (limbs[2] & mask) | (r2 & !mask);
limbs[3] = (limbs[3] & mask) | (r3 & !mask);
Fe25519(limbs)
}
fn add(self, rhs: Self) -> Self {
let (r0, carry) = self.0[0].overflowing_add(rhs.0[0]);
let (r1, carry) = adc(self.0[1], rhs.0[1], carry);
let (r2, carry) = adc(self.0[2], rhs.0[2], carry);
let (r3, _carry) = adc(self.0[3], rhs.0[3], carry);
Fe25519::reduce([r0, r1, r2, r3])
}
fn sub(self, rhs: Self) -> Self {
let (r0, borrow) = self.0[0].overflowing_sub(rhs.0[0]);
let (r1, borrow) = sbb(self.0[1], rhs.0[1], borrow);
let (r2, borrow) = sbb(self.0[2], rhs.0[2], borrow);
let (r3, borrow) = sbb(self.0[3], rhs.0[3], borrow);
let mask = if borrow { u64::MAX } else { 0 };
let (r0, carry) = r0.overflowing_add(P[0] & mask);
let (r1, carry) = adc(r1, P[1] & mask, carry);
let (r2, carry) = adc(r2, P[2] & mask, carry);
let (r3, _) = adc(r3, P[3] & mask, carry);
Fe25519([r0, r1, r2, r3])
}
fn neg(self) -> Self {
Fe25519::ZERO.sub(self)
}
fn mul(self, rhs: Self) -> Self {
let wide = mul256(self.0, rhs.0);
fe_reduce_wide(wide)
}
fn square(self) -> Self {
self.mul(self)
}
fn pow(self, exp: [u64; 4]) -> Self {
let mut result = Fe25519::ONE;
let mut base = self;
for i in 0..4 {
let mut e = exp[i];
for _ in 0..64 {
if e & 1 == 1 {
result = result.mul(base);
}
base = base.square();
e >>= 1;
}
}
result
}
fn inv(self) -> Self {
let pm2: [u64; 4] = [
0xFFFF_FFFF_FFFF_FFEB,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0x7FFF_FFFF_FFFF_FFFF,
];
self.pow(pm2)
}
fn equals(self, other: Self) -> bool {
let a = Fe25519::reduce(self.0);
let b = Fe25519::reduce(other.0);
let mut acc = 0u64;
for i in 0..4 {
acc |= a.0[i] ^ b.0[i];
}
acc == 0
}
fn is_zero(self) -> bool {
self.equals(Fe25519::ZERO)
}
fn to_bytes(self) -> [u8; 32] {
let r = Fe25519::reduce(self.0);
let mut out = [0u8; 32];
out[0..8].copy_from_slice(&r.0[0].to_le_bytes());
out[8..16].copy_from_slice(&r.0[1].to_le_bytes());
out[16..24].copy_from_slice(&r.0[2].to_le_bytes());
out[24..32].copy_from_slice(&r.0[3].to_le_bytes());
out
}
fn from_bytes(bytes: &[u8; 32]) -> Self {
let mut limbs = [0u64; 4];
limbs[0] = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
limbs[1] = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
limbs[2] = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
limbs[3] = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
limbs[3] &= 0x7FFF_FFFF_FFFF_FFFF;
Fe25519::reduce(limbs)
}
fn from_u64(v: u64) -> Self {
Fe25519([v, 0, 0, 0])
}
fn is_odd(self) -> u8 {
let r = Fe25519::reduce(self.0);
(r.0[0] & 1) as u8
}
fn ct_select(a: Self, b: Self, choice: u8) -> Self {
let mask = (choice as u64).wrapping_neg();
Fe25519([
a.0[0] ^ (mask & (a.0[0] ^ b.0[0])),
a.0[1] ^ (mask & (a.0[1] ^ b.0[1])),
a.0[2] ^ (mask & (a.0[2] ^ b.0[2])),
a.0[3] ^ (mask & (a.0[3] ^ b.0[3])),
])
}
}
#[inline(always)]
fn adc(a: u64, b: u64, carry_in: bool) -> (u64, bool) {
let (s1, c1) = a.overflowing_add(b);
let (s2, c2) = s1.overflowing_add(carry_in as u64);
(s2, c1 | c2)
}
#[inline(always)]
fn sbb(a: u64, b: u64, borrow_in: bool) -> (u64, bool) {
let (s1, b1) = a.overflowing_sub(b);
let (s2, b2) = s1.overflowing_sub(borrow_in as u64);
(s2, b1 | b2)
}
#[inline(always)]
fn mul64(a: u64, b: u64) -> u128 {
(a as u128) * (b as u128)
}
fn mul256(a: [u64; 4], b: [u64; 4]) -> [u64; 8] {
let mut r = [0u64; 8];
for i in 0..4 {
let mut carry = 0u64;
for j in 0..4 {
let uv = mul64(a[i], b[j]) + r[i + j] as u128 + carry as u128;
r[i + j] = uv as u64;
carry = (uv >> 64) as u64;
}
r[i + 4] = carry;
}
r
}
fn fe_reduce_wide(w: [u64; 8]) -> Fe25519 {
let low3_top_bit = (w[3] >> 63) & 1;
let low = [w[0], w[1], w[2], w[3] & 0x7FFF_FFFF_FFFF_FFFF];
let high = [
(w[4] << 1) | low3_top_bit,
(w[5] << 1) | (w[4] >> 63),
(w[6] << 1) | (w[5] >> 63),
(w[7] << 1) | (w[6] >> 63),
w[7] >> 63,
];
let mut acc = [0u64; 5];
let mut carry = 0u128;
for i in 0..5 {
carry += low.get(i).copied().unwrap_or(0) as u128 + 19u128 * high[i] as u128;
acc[i] = carry as u64;
carry >>= 64;
}
let top_bit = (acc[3] >> 63) & 1;
acc[3] &= 0x7FFF_FFFF_FFFF_FFFF;
let extra = (acc[4] << 1) | top_bit;
let add = extra as u128 * 19;
let (r0, c) = acc[0].overflowing_add(add as u64);
let (r1, c) = adc(acc[1], (add >> 64) as u64, c);
let (r2, c) = adc(acc[2], 0, c);
let (r3, _) = adc(acc[3], 0, c);
Fe25519::reduce([r0, r1, r2, r3])
}
const L: [u64; 4] = [
0x5812631A5CF5D3ED,
0x14DEF9DEA2F79CD6,
0x0000000000000000,
0x1000000000000000,
];
fn sc_reduce(input: &[u8; 64]) -> [u8; 32] {
let mut a = [0u64; 8];
for i in 0..8 {
a[i] = u64::from_le_bytes(input[i * 8..(i + 1) * 8].try_into().unwrap());
}
let result = bn_mod(&a, 8);
let mut out = [0u8; 32];
for i in 0..4 {
out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
}
out
}
fn sc_add(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
let mut al = [0u64; 4];
let mut bl = [0u64; 4];
for i in 0..4 {
al[i] = u64::from_le_bytes(a[i * 8..(i + 1) * 8].try_into().unwrap());
bl[i] = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
}
let (r0, carry) = al[0].overflowing_add(bl[0]);
let (r1, carry) = adc(al[1], bl[1], carry);
let (r2, carry) = adc(al[2], bl[2], carry);
let (r3, carry) = adc(al[3], bl[3], carry);
let mut result = [r0, r1, r2, r3];
let (s0, borrow) = result[0].overflowing_sub(L[0]);
let (s1, borrow) = sbb(result[1], L[1], borrow);
let (s2, borrow) = sbb(result[2], L[2], borrow);
let (s3, borrow) = sbb(result[3], L[3], borrow);
let use_sub = carry | !borrow;
let mask = if use_sub { 0u64 } else { u64::MAX };
result[0] = (result[0] & mask) | (s0 & !mask);
result[1] = (result[1] & mask) | (s1 & !mask);
result[2] = (result[2] & mask) | (s2 & !mask);
result[3] = (result[3] & mask) | (s3 & !mask);
let mut out = [0u8; 32];
for i in 0..4 {
out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
}
out
}
fn sc_mul(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
let mut al = [0u64; 4];
let mut bl = [0u64; 4];
for i in 0..4 {
al[i] = u64::from_le_bytes(a[i * 8..(i + 1) * 8].try_into().unwrap());
bl[i] = u64::from_le_bytes(b[i * 8..(i + 1) * 8].try_into().unwrap());
}
let wide = mul256(al, bl);
let result = bn_mod(&wide, 8);
let mut out = [0u8; 32];
for i in 0..4 {
out[i * 8..(i + 1) * 8].copy_from_slice(&result[i].to_le_bytes());
}
out
}
fn bn_mod(a: &[u64], limbs: usize) -> [u64; 4] {
let mut r = [0u64; 5];
for word_idx in (0..limbs).rev() {
for bit_idx in (0..64).rev() {
let mut carry = 0u64;
for i in 0..5 {
let new_carry = r[i] >> 63;
r[i] = (r[i] << 1) | carry;
carry = new_carry;
}
r[0] |= (a[word_idx] >> bit_idx) & 1;
let (s0, b) = r[0].overflowing_sub(L[0]);
let (s1, b) = sbb(r[1], L[1], b);
let (s2, b) = sbb(r[2], L[2], b);
let (s3, b) = sbb(r[3], L[3], b);
let (s4, b) = sbb(r[4], 0, b);
if !b {
r[0] = s0;
r[1] = s1;
r[2] = s2;
r[3] = s3;
r[4] = s4;
}
}
}
[r[0], r[1], r[2], r[3]]
}
#[derive(Clone, Copy, Debug)]
struct ExtPoint {
x: Fe25519,
y: Fe25519,
z: Fe25519,
t: Fe25519,
}
const D: Fe25519 = Fe25519([
0x75EB4DCA135978A3,
0x00700A4D4141D8AB,
0x8CC740797779E898,
0x52036CEE2B6FFE73,
]);
#[cfg(test)]
const D2: Fe25519 = Fe25519([
0xEBD69B9426B2F159,
0x00E0149A8283B156,
0x198E80F2EEF3D130,
0x2406D9DC56DFFCE7,
]);
const B_X: Fe25519 = Fe25519([
0xC9562D608F25D51A,
0x692CC7609525A7B2,
0xC0A4E231FDD6DC5C,
0x216936D3CD6E53FE,
]);
const B_Y: Fe25519 = Fe25519([
0x6666666666666658,
0x6666666666666666,
0x6666666666666666,
0x6666666666666666,
]);
impl ExtPoint {
const IDENTITY: ExtPoint = ExtPoint {
x: Fe25519::ZERO,
y: Fe25519::ONE,
z: Fe25519::ONE,
t: Fe25519::ZERO,
};
fn from_affine(x: Fe25519, y: Fe25519) -> Self {
ExtPoint {
x,
y,
z: Fe25519::ONE,
t: x.mul(y),
}
}
fn basepoint() -> Self {
ExtPoint::from_affine(B_X, B_Y)
}
fn double(self) -> Self {
let a = self.x.square();
let b = self.y.square();
let c = self.z.square().add(self.z.square()); let d = a.neg(); let e = self.x.add(self.y).square().sub(a).sub(b);
let g = d.add(b);
let f = g.sub(c);
let h = d.sub(b);
ExtPoint {
x: e.mul(f),
y: g.mul(h),
t: e.mul(h),
z: f.mul(g),
}
}
fn add(self, other: Self) -> Self {
let a = self.x.mul(other.x);
let b = self.y.mul(other.y);
let c = self.t.mul(other.t).mul(D);
let dd = self.z.mul(other.z);
let e = (self.x.add(self.y)).mul(other.x.add(other.y)).sub(a).sub(b);
let f = dd.sub(c);
let g = dd.add(c);
let h = b.add(a);
ExtPoint {
x: e.mul(f),
y: g.mul(h),
t: e.mul(h),
z: f.mul(g),
}
}
fn scalar_mul(self, scalar: &[u8; 32]) -> Self {
let mut result = ExtPoint::IDENTITY;
for byte_idx in (0..32).rev() {
for bit_idx in (0..8).rev() {
result = result.double();
let bit = (scalar[byte_idx] >> bit_idx) & 1;
let candidate = result.add(self);
result = ExtPoint::ct_select(result, candidate, bit);
}
}
result
}
fn ct_select(a: Self, b: Self, choice: u8) -> Self {
ExtPoint {
x: Fe25519::ct_select(a.x, b.x, choice),
y: Fe25519::ct_select(a.y, b.y, choice),
z: Fe25519::ct_select(a.z, b.z, choice),
t: Fe25519::ct_select(a.t, b.t, choice),
}
}
fn compress(self) -> [u8; 32] {
let z_inv = self.z.inv();
let x = self.x.mul(z_inv);
let y = self.y.mul(z_inv);
let mut bytes = y.to_bytes();
bytes[31] |= x.is_odd() << 7;
bytes
}
fn decompress(bytes: &[u8; 32]) -> Option<Self> {
let x_sign = (bytes[31] >> 7) & 1;
let mut y_bytes = *bytes;
y_bytes[31] &= 0x7F;
let y = Fe25519::from_bytes(&y_bytes);
let y2 = y.square();
let u = y2.sub(Fe25519::ONE); let v = D.mul(y2).add(Fe25519::ONE);
let v3 = v.square().mul(v);
let v7 = v3.square().mul(v);
let uv7 = u.mul(v7);
let exp: [u64; 4] = [
0xFFFF_FFFF_FFFF_FFFD,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0x0FFF_FFFF_FFFF_FFFF,
];
let uv7_pow = uv7.pow(exp);
let mut x = u.mul(v3).mul(uv7_pow);
let check = v.mul(x.square());
if check.equals(u) {
} else if check.equals(u.neg()) {
let sqrt_m1 = compute_sqrt_m1();
x = x.mul(sqrt_m1);
} else {
return None;
}
if x.is_zero() && x_sign == 1 {
return None;
}
if x.is_odd() != x_sign {
x = x.neg();
}
let t = x.mul(y);
Some(ExtPoint {
x,
y,
z: Fe25519::ONE,
t,
})
}
fn equals(self, other: Self) -> bool {
let lhs_x = self.x.mul(other.z);
let rhs_x = other.x.mul(self.z);
let lhs_y = self.y.mul(other.z);
let rhs_y = other.y.mul(self.z);
lhs_x.equals(rhs_x) && lhs_y.equals(rhs_y)
}
}
fn compute_sqrt_m1() -> Fe25519 {
let exp: [u64; 4] = [
0xFFFF_FFFF_FFFF_FFFB,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0x1FFF_FFFF_FFFF_FFFF,
];
Fe25519::from_u64(2).pow(exp)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ed25519PublicKey(pub [u8; 32]);
#[derive(Clone)]
pub struct Ed25519SecretKey(pub [u8; 32]);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ed25519Signature(pub [u8; 64]);
pub fn ed25519_keygen(secret: &[u8; 32]) -> (Ed25519PublicKey, Ed25519SecretKey) {
let hash = Sha512::hash(secret);
let mut a = [0u8; 32];
a.copy_from_slice(&hash[..32]);
a[0] &= 248;
a[31] &= 127;
a[31] |= 64;
let big_a = ExtPoint::basepoint().scalar_mul(&a);
let pk_bytes = big_a.compress();
(Ed25519PublicKey(pk_bytes), Ed25519SecretKey(*secret))
}
fn update_dom2(hasher: &mut Sha512, dom2: Option<&[u8]>) {
if let Some(prefix) = dom2 {
hasher.update(prefix);
}
}
fn ed25519_sign_internal(sk: &Ed25519SecretKey, message_or_hash: &[u8], dom2: Option<&[u8]>) -> Ed25519Signature {
let hash = Sha512::hash(&sk.0);
let mut a = [0u8; 32];
a.copy_from_slice(&hash[..32]);
a[0] &= 248;
a[31] &= 127;
a[31] |= 64;
let prefix = &hash[32..64];
let big_a = ExtPoint::basepoint().scalar_mul(&a);
let pk_bytes = big_a.compress();
let mut hasher = Sha512::new();
update_dom2(&mut hasher, dom2);
hasher.update(prefix);
hasher.update(message_or_hash);
let r_hash = hasher.finalize();
let mut r_wide = [0u8; 64];
r_wide.copy_from_slice(&r_hash);
let r_scalar = sc_reduce(&r_wide);
let big_r = ExtPoint::basepoint().scalar_mul(&r_scalar);
let r_bytes = big_r.compress();
let mut hasher = Sha512::new();
update_dom2(&mut hasher, dom2);
hasher.update(&r_bytes);
hasher.update(&pk_bytes);
hasher.update(message_or_hash);
let k_hash = hasher.finalize();
let mut k_wide = [0u8; 64];
k_wide.copy_from_slice(&k_hash);
let k = sc_reduce(&k_wide);
let ka = sc_mul(&k, &a);
let s = sc_add(&r_scalar, &ka);
let mut sig = [0u8; 64];
sig[..32].copy_from_slice(&r_bytes);
sig[32..].copy_from_slice(&s);
Ed25519Signature(sig)
}
fn ed25519_verify_internal(
pk: &Ed25519PublicKey,
message_or_hash: &[u8],
sig: &Ed25519Signature,
dom2: Option<&[u8]>,
) -> bool {
let r_bytes: [u8; 32] = match sig.0[..32].try_into() {
Ok(b) => b,
Err(_) => return false,
};
let big_r = match ExtPoint::decompress(&r_bytes) {
Some(p) => p,
None => return false,
};
let big_a = match ExtPoint::decompress(&pk.0) {
Some(p) => p,
None => return false,
};
let s_bytes: [u8; 32] = match sig.0[32..].try_into() {
Ok(b) => b,
Err(_) => return false,
};
{
let mut sl = [0u64; 4];
for i in 0..4 {
sl[i] = u64::from_le_bytes(s_bytes[i * 8..(i + 1) * 8].try_into().unwrap());
}
let (_, borrow) = sl[0].overflowing_sub(L[0]);
let (_, borrow) = sbb(sl[1], L[1], borrow);
let (_, borrow) = sbb(sl[2], L[2], borrow);
let (_, borrow) = sbb(sl[3], L[3], borrow);
if !borrow {
return false; }
}
let mut hasher = Sha512::new();
update_dom2(&mut hasher, dom2);
hasher.update(&r_bytes);
hasher.update(&pk.0);
hasher.update(message_or_hash);
let k_hash = hasher.finalize();
let mut k_wide = [0u8; 64];
k_wide.copy_from_slice(&k_hash);
let k = sc_reduce(&k_wide);
let sb = ExtPoint::basepoint().scalar_mul(&s_bytes);
let ka = big_a.scalar_mul(&k);
let rhs = big_r.add(ka);
let lhs = sb.double().double().double();
let rhs = rhs.double().double().double();
lhs.equals(rhs)
}
const DOM2_LITERAL: &[u8] = b"SigEd25519 no Ed25519 collisions";
fn build_dom2(f: u8, context: &[u8]) -> Option<Vec<u8>> {
if context.len() > 255 {
return None;
}
let mut out = Vec::with_capacity(DOM2_LITERAL.len() + 2 + context.len());
out.extend_from_slice(DOM2_LITERAL);
out.push(f);
out.push(context.len() as u8);
out.extend_from_slice(context);
Some(out)
}
pub fn ed25519_sign(sk: &Ed25519SecretKey, msg: &[u8]) -> Ed25519Signature {
ed25519_sign_internal(sk, msg, None)
}
pub fn ed25519_verify(pk: &Ed25519PublicKey, msg: &[u8], sig: &Ed25519Signature) -> bool {
ed25519_verify_internal(pk, msg, sig, None)
}
pub fn ed25519ctx_sign(sk: &Ed25519SecretKey, msg: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
if context.is_empty() {
return None;
}
let dom2 = build_dom2(0, context)?;
Some(ed25519_sign_internal(sk, msg, Some(&dom2)))
}
pub fn ed25519ctx_verify(pk: &Ed25519PublicKey, msg: &[u8], context: &[u8], sig: &Ed25519Signature) -> bool {
if context.is_empty() || context.len() > 255 {
return false;
}
let dom2 = match build_dom2(0, context) {
Some(d) => d,
None => return false,
};
ed25519_verify_internal(pk, msg, sig, Some(&dom2))
}
pub fn ed25519ph_sign(sk: &Ed25519SecretKey, msg: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
let dom2 = build_dom2(1, context)?;
let m_prime = Sha512::hash(msg);
Some(ed25519_sign_internal(sk, &m_prime, Some(&dom2)))
}
pub fn ed25519ph_verify(pk: &Ed25519PublicKey, msg: &[u8], context: &[u8], sig: &Ed25519Signature) -> bool {
if context.len() > 255 {
return false;
}
let dom2 = match build_dom2(1, context) {
Some(d) => d,
None => return false,
};
let m_prime = Sha512::hash(msg);
ed25519_verify_internal(pk, &m_prime, sig, Some(&dom2))
}
pub fn ed25519ph_sign_prehashed(sk: &Ed25519SecretKey, prehashed: &[u8], context: &[u8]) -> Option<Ed25519Signature> {
if prehashed.len() != 64 {
return None;
}
let dom2 = build_dom2(1, context)?;
Some(ed25519_sign_internal(sk, prehashed, Some(&dom2)))
}
pub fn ed25519ph_verify_prehashed(
pk: &Ed25519PublicKey,
prehashed: &[u8],
context: &[u8],
sig: &Ed25519Signature,
) -> bool {
if prehashed.len() != 64 || context.len() > 255 {
return false;
}
let dom2 = match build_dom2(1, context) {
Some(d) => d,
None => return false,
};
ed25519_verify_internal(pk, prehashed, sig, Some(&dom2))
}
#[cfg(test)]
mod tests {
use super::*;
fn hex_to_bytes(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn test_fe25519_basic() {
let a = Fe25519::ONE;
let b = Fe25519::ONE;
let c = a.add(b);
assert_eq!(c.0[0], 2);
assert_eq!(c.0[1], 0);
let d = c.sub(a);
assert!(d.equals(Fe25519::ONE));
}
#[test]
fn test_fe25519_mul() {
let a = Fe25519::from_u64(7);
let b = Fe25519::from_u64(13);
let c = a.mul(b);
assert!(c.equals(Fe25519::from_u64(91)));
}
#[test]
fn test_fe25519_inv() {
let a = Fe25519::from_u64(42);
let b = a.inv();
let c = a.mul(b);
assert!(c.equals(Fe25519::ONE));
}
#[test]
fn test_basepoint_on_curve() {
let x2 = B_X.square();
let y2 = B_Y.square();
let lhs = y2.sub(x2);
let rhs = Fe25519::ONE.add(D.mul(x2).mul(y2));
assert!(lhs.equals(rhs), "Base point not on curve!");
}
#[test]
fn test_d_constant() {
let n = Fe25519::from_u64(121665).neg();
let d_inv = Fe25519::from_u64(121666).inv();
let d_computed = n.mul(d_inv);
assert!(d_computed.equals(D), "D constant is wrong");
}
#[test]
fn test_d2_constant() {
let d2_computed = D.add(D);
assert!(d2_computed.equals(D2), "D2 constant is wrong");
}
#[test]
fn test_basepoint_compress_decompress() {
let bp = ExtPoint::basepoint();
let compressed = bp.compress();
let decompressed = ExtPoint::decompress(&compressed).expect("decompress failed");
assert!(bp.equals(decompressed));
}
fn naive_scalar_mul(p: ExtPoint, scalar: &[u8; 32]) -> ExtPoint {
let mut acc = ExtPoint::IDENTITY;
let mut started = false;
for byte_idx in (0..32).rev() {
for bit_idx in (0..8).rev() {
if started {
acc = acc.double();
}
let bit = (scalar[byte_idx] >> bit_idx) & 1;
if bit == 1 {
if !started {
acc = p;
started = true;
} else {
acc = acc.add(p);
}
}
}
}
acc
}
#[test]
fn test_scalar_mul_vs_naive() {
let bp = ExtPoint::basepoint();
let mut s = [0u8; 32];
for i in 0..32 {
s[i] = (i as u8) ^ 0xa5;
}
s[31] &= 0x7f;
let r1 = bp.scalar_mul(&s);
let r2 = naive_scalar_mul(bp, &s);
assert!(r1.equals(r2));
}
#[test]
fn test_sc_reduce_basic() {
let mut x = [0u8; 64];
x[0] = 1;
let r = sc_reduce(&x);
assert_eq!(r[0], 1);
for i in 1..32 {
assert_eq!(r[i], 0);
}
let mut x = [0u8; 64];
x[0..8].copy_from_slice(&L[0].to_le_bytes());
x[8..16].copy_from_slice(&L[1].to_le_bytes());
x[16..24].copy_from_slice(&L[2].to_le_bytes());
x[24..32].copy_from_slice(&L[3].to_le_bytes());
let r = sc_reduce(&x);
for i in 0..32 {
assert_eq!(r[i], 0, "L mod L should be 0, byte {}", i);
}
let mut x = [0u8; 64];
x[0..8].copy_from_slice(&(L[0] + 1).to_le_bytes());
x[8..16].copy_from_slice(&L[1].to_le_bytes());
x[16..24].copy_from_slice(&L[2].to_le_bytes());
x[24..32].copy_from_slice(&L[3].to_le_bytes());
let r = sc_reduce(&x);
assert_eq!(r[0], 1);
}
#[test]
fn test_fe_pow_fermat() {
let a = Fe25519::from_u64(7);
let pm1: [u64; 4] = [
0xFFFF_FFFF_FFFF_FFEC,
0xFFFF_FFFF_FFFF_FFFF,
0xFFFF_FFFF_FFFF_FFFF,
0x7FFF_FFFF_FFFF_FFFF,
];
let r = a.pow(pm1);
assert!(r.equals(Fe25519::ONE));
}
#[test]
fn test_scalar_mul_two() {
let bp = ExtPoint::basepoint();
let mut two = [0u8; 32];
two[0] = 2;
let r = bp.scalar_mul(&two);
assert!(r.equals(bp.double()));
}
#[test]
fn test_scalar_mul_three() {
let bp = ExtPoint::basepoint();
let mut three = [0u8; 32];
three[0] = 3;
let r = bp.scalar_mul(&three);
assert!(r.equals(bp.double().add(bp)));
}
#[test]
fn test_scalar_mul_identity() {
let bp = ExtPoint::basepoint();
let mut one = [0u8; 32];
one[0] = 1;
let result = bp.scalar_mul(&one);
assert!(result.equals(bp));
}
#[test]
fn test_point_add_double() {
let bp = ExtPoint::basepoint();
let bp2_add = bp.add(bp);
let bp2_dbl = bp.double();
assert!(bp2_add.equals(bp2_dbl));
}
#[test]
fn test_ed25519_vector1() {
let sk_hex = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
let pk_hex = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
let sig_hex = "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155\
5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b";
let sk_bytes = hex_to_bytes(sk_hex);
let pk_bytes = hex_to_bytes(pk_hex);
let sig_bytes = hex_to_bytes(sig_hex);
let mut sk = [0u8; 32];
sk.copy_from_slice(&sk_bytes);
let (pk, secret) = ed25519_keygen(&sk);
assert_eq!(&pk.0[..], &pk_bytes[..], "Public key mismatch");
let signature = ed25519_sign(&secret, b"");
assert_eq!(&signature.0[..], &sig_bytes[..], "Signature mismatch");
assert!(ed25519_verify(&pk, b"", &signature), "Verification failed");
}
#[test]
fn test_ed25519_vector2() {
let sk_hex = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb";
let pk_hex = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
let sig_hex = "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da\
085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00";
let sk_bytes = hex_to_bytes(sk_hex);
let pk_bytes = hex_to_bytes(pk_hex);
let sig_bytes = hex_to_bytes(sig_hex);
let mut sk = [0u8; 32];
sk.copy_from_slice(&sk_bytes);
let (pk, secret) = ed25519_keygen(&sk);
assert_eq!(&pk.0[..], &pk_bytes[..], "Public key mismatch");
let msg = [0x72u8];
let signature = ed25519_sign(&secret, &msg);
assert_eq!(&signature.0[..], &sig_bytes[..], "Signature mismatch");
assert!(ed25519_verify(&pk, &msg, &signature), "Verification failed");
}
#[test]
fn rfc8032_section_7_2_ed25519ctx_vector() {
let sk_bytes = hex_to_bytes("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6");
let pk_bytes = hex_to_bytes("dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292");
let msg = hex_to_bytes("f726936d19c800494e3fdaff20b276a8");
let context = hex_to_bytes("666f6f"); let sig_bytes = hex_to_bytes(
"55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a\
8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d",
);
let mut sk_arr = [0u8; 32];
sk_arr.copy_from_slice(&sk_bytes);
let (pk, secret) = ed25519_keygen(&sk_arr);
assert_eq!(pk.0.as_slice(), pk_bytes.as_slice(), "pk mismatch");
let signature = ed25519ctx_sign(&secret, &msg, &context).expect("ctx_sign");
assert_eq!(
signature.0.as_slice(),
sig_bytes.as_slice(),
"Ed25519ctx signature mismatch"
);
assert!(ed25519ctx_verify(&pk, &msg, &context, &signature));
}
#[test]
fn rfc8032_section_7_3_ed25519ph_vector() {
let sk_bytes = hex_to_bytes("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42");
let pk_bytes = hex_to_bytes("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf");
let msg = hex_to_bytes("616263"); let sig_bytes = hex_to_bytes(
"98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae41\
31f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406",
);
let mut sk_arr = [0u8; 32];
sk_arr.copy_from_slice(&sk_bytes);
let (pk, secret) = ed25519_keygen(&sk_arr);
assert_eq!(pk.0.as_slice(), pk_bytes.as_slice(), "pk mismatch");
let signature = ed25519ph_sign(&secret, &msg, b"").expect("ph_sign");
assert_eq!(
signature.0.as_slice(),
sig_bytes.as_slice(),
"Ed25519ph signature mismatch"
);
assert!(ed25519ph_verify(&pk, &msg, b"", &signature));
}
#[test]
fn ed25519ph_prehashed_matches_message_form() {
let sk_bytes = hex_to_bytes("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42");
let mut sk_arr = [0u8; 32];
sk_arr.copy_from_slice(&sk_bytes);
let (pk, secret) = ed25519_keygen(&sk_arr);
let from_msg = ed25519ph_sign(&secret, b"abc", b"").expect("ph_sign");
let digest = Sha512::hash(b"abc");
let from_digest = ed25519ph_sign_prehashed(&secret, &digest, b"").expect("ph_sign_prehashed");
assert_eq!(from_msg.0, from_digest.0);
assert!(ed25519ph_verify(&pk, b"abc", b"", &from_msg));
assert!(ed25519ph_verify_prehashed(&pk, &digest, b"", &from_msg));
}
fn make_test_keys() -> (Ed25519PublicKey, Ed25519SecretKey) {
let sk_bytes = hex_to_bytes("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6");
let mut sk = [0u8; 32];
sk.copy_from_slice(&sk_bytes);
ed25519_keygen(&sk)
}
#[test]
fn ed25519ctx_requires_nonempty_context() {
let (_pk, sk) = make_test_keys();
assert!(ed25519ctx_sign(&sk, b"any message", b"").is_none());
}
#[test]
fn ed25519ctx_signature_does_not_verify_as_pure() {
let (pk, sk) = make_test_keys();
let msg = b"shared message";
let sig = ed25519ctx_sign(&sk, msg, b"appA").expect("ctx_sign");
assert!(!ed25519_verify(&pk, msg, &sig));
}
#[test]
fn pure_signature_does_not_verify_as_ctx() {
let (pk, sk) = make_test_keys();
let msg = b"shared message";
let sig = ed25519_sign(&sk, msg);
assert!(!ed25519ctx_verify(&pk, msg, b"appA", &sig));
}
#[test]
fn ed25519ctx_different_contexts_dont_cross() {
let (pk, sk) = make_test_keys();
let msg = b"shared message";
let sig_a = ed25519ctx_sign(&sk, msg, b"appA").expect("ctx_sign A");
let sig_b = ed25519ctx_sign(&sk, msg, b"appB").expect("ctx_sign B");
assert!(ed25519ctx_verify(&pk, msg, b"appA", &sig_a));
assert!(ed25519ctx_verify(&pk, msg, b"appB", &sig_b));
assert!(!ed25519ctx_verify(&pk, msg, b"appB", &sig_a));
assert!(!ed25519ctx_verify(&pk, msg, b"appA", &sig_b));
assert_ne!(sig_a.0, sig_b.0);
}
#[test]
fn ed25519ph_signature_does_not_verify_as_pure() {
let (pk, sk) = make_test_keys();
let msg = b"shared message";
let sig = ed25519ph_sign(&sk, msg, b"").expect("ph_sign");
assert!(!ed25519_verify(&pk, msg, &sig));
}
#[test]
fn ed25519ph_signature_does_not_verify_as_ctx() {
let (pk, sk) = make_test_keys();
let msg = b"shared message";
let sig = ed25519ph_sign(&sk, msg, b"context").expect("ph_sign");
assert!(!ed25519ctx_verify(&pk, msg, b"context", &sig));
}
#[test]
fn ed25519ctx_max_context_length() {
let (pk, sk) = make_test_keys();
let msg = b"x";
let ctx_max = vec![0xa5u8; 255];
let sig = ed25519ctx_sign(&sk, msg, &ctx_max).expect("ctx max len");
assert!(ed25519ctx_verify(&pk, msg, &ctx_max, &sig));
let ctx_too_long = vec![0xa5u8; 256];
assert!(ed25519ctx_sign(&sk, msg, &ctx_too_long).is_none());
assert!(!ed25519ctx_verify(&pk, msg, &ctx_too_long, &sig));
}
}