use crate::error::{AppError, Result};
use argon2::{
Argon2,
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
};
use base64::Engine;
use curve25519_dalek::montgomery::MontgomeryPoint;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use rand::{RngCore, rngs::OsRng};
use sha2::{Digest, Sha256};
pub const XEDDSA_SIGN_BIT_MASK: u8 = 0x7F;
pub const XEDDSA_SIGN_BIT: u8 = 0x80;
pub fn hash_password(password: &str) -> Result<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2.hash_password(password.as_bytes(), &salt).map_err(|_| AppError::Internal)?.to_string();
Ok(password_hash)
}
pub fn verify_password(password: &str, password_hash: &str) -> Result<bool> {
let parsed_hash = PasswordHash::new(password_hash).map_err(|_| AppError::Internal)?;
Ok(Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok())
}
pub fn verify_signature(public_key_bytes: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<()> {
let public_key = VerifyingKey::from_bytes(
public_key_bytes.try_into().map_err(|_| AppError::BadRequest("Invalid public key length".into()))?,
)
.map_err(|_| AppError::BadRequest("Invalid public key".into()))?;
let signature = Signature::from_bytes(
signature_bytes.try_into().map_err(|_| AppError::BadRequest("Invalid signature length".into()))?,
);
public_key.verify(message, &signature).map_err(|_| AppError::BadRequest("Invalid signature".into()))?;
Ok(())
}
pub fn verify_signature_with_montgomery(
public_key_bytes: &[u8],
message: &[u8],
signature_bytes: &[u8],
) -> Result<()> {
let mont_bytes: [u8; 32] =
public_key_bytes.try_into().map_err(|_| AppError::BadRequest("Invalid public key length".into()))?;
let mont_point = MontgomeryPoint(mont_bytes);
let mut signature_bytes_fixed: [u8; 64] = signature_bytes.try_into().map_err(|_| AppError::BadRequest("Invalid signature length".into()))?;
signature_bytes_fixed[63] &= XEDDSA_SIGN_BIT_MASK;
let signature = Signature::from_bytes(&signature_bytes_fixed);
tracing::debug!("verify_signature_with_montgomery: message_len={}, signature_len={}", message.len(), signature_bytes.len());
if let Some(ed_point) = mont_point.to_edwards(0) {
let ed_bytes = ed_point.compress().to_bytes();
if let Ok(public_key) = VerifyingKey::from_bytes(&ed_bytes) {
if public_key.verify(message, &signature).is_ok() {
return Ok(());
}
}
}
if let Some(ed_point) = mont_point.to_edwards(1) {
let ed_bytes = ed_point.compress().to_bytes();
if let Ok(public_key) = VerifyingKey::from_bytes(&ed_bytes) {
if public_key.verify(message, &signature).is_ok() {
return Ok(());
}
}
}
Err(AppError::BadRequest("Invalid signature".into()))
}
pub fn generate_opaque_token() -> String {
let mut bytes = [0u8; 32];
OsRng.fill_bytes(&mut bytes);
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
}
pub fn hash_token(token: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(token.as_bytes());
hex::encode(hasher.finalize())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::crypto_types::DJB_KEY_PREFIX;
use ed25519_dalek::{Signer, SigningKey};
#[test]
fn test_verify_signature_strictness() {
let mut ik_bytes = [0u8; 32];
OsRng.fill_bytes(&mut ik_bytes);
let identity_key = SigningKey::from_bytes(&ik_bytes);
let ik_pub = identity_key.verifying_key().to_bytes().to_vec();
let mut spk_bytes = [0u8; 32];
OsRng.fill_bytes(&mut spk_bytes);
let spk_key = SigningKey::from_bytes(&spk_bytes);
let spk_pub_32 = spk_key.verifying_key().to_bytes().to_vec();
let signature = identity_key.sign(&spk_pub_32).to_bytes().to_vec();
let mut spk_pub_33 = spk_pub_32.clone();
spk_pub_33.insert(0, DJB_KEY_PREFIX);
let res1 = verify_signature(&ik_pub, &spk_pub_32, &signature);
assert!(res1.is_ok(), "Case 1 failed: Standard 32-byte verification");
let res2 = verify_signature(&ik_pub, &spk_pub_33, &signature);
assert!(res2.is_err(), "Case 2 failed: verify_signature should NOT accept 33-byte messages implicitly");
let mut ik_pub_33 = ik_pub.clone();
ik_pub_33.insert(0, DJB_KEY_PREFIX);
let res3 = verify_signature(&ik_pub_33, &spk_pub_32, &signature);
assert!(res3.is_err(), "Case 3 failed: verify_signature should NOT accept 33-byte verifier keys implicitly");
}
#[test]
fn test_verify_signature_with_high_bit_set() {
use curve25519_dalek::edwards::CompressedEdwardsY;
let mut ik_bytes = [0u8; 32];
OsRng.fill_bytes(&mut ik_bytes);
let identity_key = SigningKey::from_bytes(&ik_bytes);
let ik_pub_ed = identity_key.verifying_key().to_bytes();
let ed_point = CompressedEdwardsY(ik_pub_ed).decompress().unwrap();
let mont_point = ed_point.to_montgomery();
let ik_pub_x25519 = mont_point.to_bytes();
let message = b"test message";
let mut signature_bytes = identity_key.sign(message).to_bytes();
signature_bytes[63] |= XEDDSA_SIGN_BIT;
let res = verify_signature_with_montgomery(&ik_pub_x25519, message, &signature_bytes);
assert!(res.is_ok(), "Should verify signature even with high bit set by clearing it internally");
}
}