use ed25519_dalek::{Signature, Signer, Verifier};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SignError {
Invalid(String),
Randomness(&'static str),
}
impl core::fmt::Display for SignError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SignError::Invalid(msg) => write!(f, "审计签名: 非法输入: {msg}"),
SignError::Randomness(src) => write!(f, "审计签名: 随机种子失败: {src}"),
}
}
}
impl std::error::Error for SignError {}
#[derive(Debug, Clone)]
pub struct AuditSigner {
signing: ed25519_dalek::SigningKey,
verifying: ed25519_dalek::VerifyingKey,
}
impl AuditSigner {
pub fn from_bytes(seed: [u8; 32]) -> Self {
let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
let verifying = signing.verifying_key();
Self { signing, verifying }
}
pub fn from_hex(seed_hex: &str) -> Result<Self, SignError> {
let bytes = hex_decode(seed_hex)?;
if bytes.len() != 32 {
return Err(SignError::Invalid(format!(
"私钥种子长度 != 32: {}",
bytes.len()
)));
}
let mut seed = [0u8; 32];
for (dst, src) in seed.iter_mut().zip(bytes.iter()) {
*dst = *src;
}
Ok(Self::from_bytes(seed))
}
pub fn generate_keys() -> Result<(String, String), SignError> {
let mut seed = [0u8; 32];
getrandom::getrandom(&mut seed)
.map_err(|_| SignError::Randomness("OS 熵源不可用"))?;
let signer = Self::from_bytes(seed);
Ok((hex_encode(&seed), hex_encode(&signer.verifying_bytes())))
}
pub fn verifying_bytes(&self) -> [u8; 32] {
self.verifying.to_bytes()
}
pub fn signature_bytes(&self, payload: &[u8]) -> [u8; 64] {
let sig: Signature = self.signing.sign(payload);
sig.to_bytes()
}
}
pub fn verify_signature(
verifying_bytes: [u8; 32],
payload: &[u8],
sig_bytes: &[u8; 64],
) -> Result<bool, SignError> {
let verifying = ed25519_dalek::VerifyingKey::from_bytes(&verifying_bytes)
.map_err(|e| SignError::Invalid(format!("公钥非法: {e}")))?;
let sig = Signature::from_bytes(sig_bytes);
Ok(verifying.verify(payload, &sig).is_ok())
}
pub(crate) fn hex_encode(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{b:02x}"));
}
s
}
pub(crate) fn hex_decode(hex: &str) -> Result<Vec<u8>, SignError> {
if hex.len() % 2 != 0 {
return Err(SignError::Invalid(format!("hex 长度为奇数: {}", hex.len())));
}
let mut out = Vec::with_capacity(hex.len() / 2);
let bytes = hex.as_bytes();
for i in (0..bytes.len()).step_by(2) {
let hi = (bytes[i] as char).to_digit(16);
let lo = (bytes[i + 1] as char).to_digit(16);
match (hi, lo) {
(Some(h), Some(l)) => out.push(((h << 4) | l) as u8),
_ => return Err(SignError::Invalid("hex 含非十六进制字符".into())),
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_signature_is_deterministic() {
let signer = AuditSigner::from_bytes([7u8; 32]);
let payload = b"audit-anchor-payload";
assert_eq!(signer.signature_bytes(payload), signer.signature_bytes(payload));
}
#[test]
fn test_sign_and_verify_ok() {
let signer = AuditSigner::from_bytes([7u8; 32]);
let pk = signer.verifying_bytes();
let payload = b"hello-audit";
let sig = signer.signature_bytes(payload);
assert!(verify_signature(pk, payload, &sig).unwrap());
}
#[test]
fn test_verify_rejects_tampered_payload() {
let signer = AuditSigner::from_bytes([7u8; 32]);
let pk = signer.verifying_bytes();
let sig = signer.signature_bytes(b"hello");
assert!(!verify_signature(pk, b"hellO", &sig).unwrap());
}
#[test]
fn test_hex_roundtrip() {
let bytes = [0u8, 1, 0xff, 0x10, 0xab, 0xcd];
let hex = hex_encode(&bytes);
assert_eq!(hex_decode(&hex).unwrap(), bytes);
assert!(hex_decode("zz").is_err());
assert!(hex_decode("abc").is_err());
}
#[test]
fn test_generate_keys_produces_hex() {
let (sk, pk) = AuditSigner::generate_keys().unwrap();
assert_eq!(sk.len(), 64);
assert_eq!(pk.len(), 64);
let signer = AuditSigner::from_hex(&sk).unwrap();
assert_eq!(signer.verifying_bytes(), hex_decode(&pk).unwrap().as_slice());
}
}