use num_bigint::BigUint;
use num_traits::{Num, One, Zero};
use sha1::{Digest, Sha1};
use subtle::{Choice, ConstantTimeEq};
use alloc::{vec, vec::Vec};
use core::ops::Sub;
pub const I2P_DSA_P: &str = "\
9C05B2AA_960D9B97_B8931963_C9CC9E8C_3026E9B8_ED92FAD0\
A69CC886_D5BF8015_FCADAE31_A0AD18FA_B3F01B00_A358DE23\
7655C496_4AFAA2B3_37E96AD3_16B9FB1C_C564B5AE_C5B69A9F\
F6C3E454_8707FEF8_503D91DD_8602E867_E6D35D22_35C1869C\
E2479C3B_9D5401DE_04E0727F_B33D6511_285D4CF2_9538D9E3\
B6051F5B_22CC1C93";
pub const I2P_DSA_Q: &str = "A5DFC28F_EF4CA1E2_86744CD8_EED9D29D_684046B7";
pub const I2P_DSA_G: &str = "\
0C1F4D27_D40093B4_29E962D7_223824E0_BBC47E7C_832A3923\
6FC683AF_84889581_075FF908_2ED32353_D4374D73_01CDA1D2\
3C431F46_98599DDA_02451824_FF369752_593647CC_3DDC197D\
E985E43D_136CDCFC_6BD5409C_D2F45082_1142A5E6_F8EB1C3A\
B5D0484B_8129FCF1_7BCE4F7F_33321C3C_B3DBB14A_905E7B2B\
3E93BE47_08CBCC82";
lazy_static::lazy_static! {
pub static ref DSA_P: BigUint = BigUint::from_str_radix(I2P_DSA_P, 16).unwrap();
pub static ref DSA_Q: BigUint = BigUint::from_str_radix(I2P_DSA_Q, 16).unwrap();
pub static ref DSA_QM2: BigUint = (&(*DSA_Q)).sub(BigUint::one()).sub(BigUint::one());
pub static ref DSA_G: BigUint = BigUint::from_str_radix(I2P_DSA_G, 16).unwrap();
}
pub fn rectify(bi: &BigUint, len: usize) -> Vec<u8> {
let mut b = bi.to_bytes_be();
match b.len() {
sz if sz == len => b,
sz if sz > len => panic!("key too big ({sz}) max is {len}"),
0 => {
vec![0u8; len]
}
_ => {
let mut ret = vec![0u8; len];
ret.truncate(len - b.len());
ret.append(&mut b);
ret
}
}
}
#[derive(Clone, Debug)]
pub struct DsaSignature {
rbar: [u8; 20],
sbar: [u8; 20],
}
impl ConstantTimeEq for DsaSignature {
fn ct_eq(&self, other: &Self) -> Choice {
self.rbar.ct_eq(&other.rbar) & self.sbar.ct_eq(&other.sbar)
}
}
impl PartialEq for DsaSignature {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl DsaSignature {
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.len() < 40 {
return None;
}
Some(DsaSignature {
rbar: TryInto::<[u8; 20]>::try_into(&data[..20]).ok()?,
sbar: TryInto::<[u8; 20]>::try_into(&data[20..40]).ok()?,
})
}
}
impl ConstantTimeEq for DsaPublicKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_bytes().ct_eq(other.as_bytes())
}
}
impl PartialEq for DsaPublicKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
#[derive(Clone, Debug, Eq)]
pub struct DsaPublicKey {
bi: BigUint,
bytes: Vec<u8>,
}
impl DsaPublicKey {
pub fn from_bytes(data: &[u8]) -> Option<Self> {
let bi = BigUint::from_bytes_be(data);
let bytes = rectify(&bi, 128);
Some(DsaPublicKey { bi, bytes })
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[allow(clippy::many_single_char_names)]
pub fn verify(&self, msg: &[u8], sig: &DsaSignature) -> bool {
let p = &(*DSA_P);
let q = &(*DSA_Q);
let r = BigUint::from_bytes_be(&sig.rbar);
let s = BigUint::from_bytes_be(&sig.sbar);
if r.is_zero() || r >= *DSA_Q || s.is_zero() || s >= *DSA_Q {
return false;
}
let w = s.modpow(&DSA_QM2, q);
let hm = BigUint::from_bytes_be(&Sha1::digest(msg));
let u1 = &w * hm % q;
let u2 = &r * &w % q;
let v = (DSA_G.modpow(&u1, p) * self.bi.modpow(&u2, p) % p) % q;
v == r
}
}
#[cfg(test)]
mod tests {
use num_bigint::BigUint;
use num_traits::{One, Zero};
use super::rectify;
#[test]
fn rectify_zero() {
assert_eq!(&rectify(&BigUint::zero(), 1), &[0]);
assert_eq!(&rectify(&BigUint::zero(), 8), &[0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(&rectify(&BigUint::zero(), 32), &[0u8; 32]);
}
#[test]
fn rectify_one() {
assert_eq!(&rectify(&BigUint::one(), 1), &[1]);
assert_eq!(&rectify(&BigUint::one(), 4), &[0, 0, 0, 1]);
assert_eq!(&rectify(&BigUint::one(), 8), &[0, 0, 0, 0, 0, 0, 0, 1]);
}
#[test]
fn rectify_value() {
let val = [0xff, 0xab, 0xcd, 0xef];
assert_eq!(&rectify(&BigUint::from_bytes_be(&val), 4), &val);
assert_eq!(
&rectify(&BigUint::from_bytes_be(&val), 5),
&[0x00, 0xff, 0xab, 0xcd, 0xef]
);
}
}