use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::string::ToString;
use alloc::{format, vec::Vec};
use ed25519_dalek::{Signature, Signer as _, SigningKey, Verifier as _, VerifyingKey};
use crate::{SignError, SignOutput};
pub struct Ed25519Signer {
key: SigningKey,
}
impl core::fmt::Debug for Ed25519Signer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Ed25519Signer")
.field("key", &"[REDACTED]")
.finish()
}
}
impl Ed25519Signer {
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, SignError> {
Ok(Self {
key: SigningKey::from_bytes(bytes),
})
}
pub fn from_hex(hex_str: &str) -> Result<Self, SignError> {
let stripped = hex_str.strip_prefix("0x").unwrap_or(hex_str);
let decoded = hex::decode(stripped).map_err(|e| SignError::InvalidKey(e.to_string()))?;
let bytes: [u8; 32] = decoded.try_into().map_err(|v: Vec<u8>| {
SignError::InvalidKey(format!("expected 32 bytes, got {}", v.len()))
})?;
Self::from_bytes(&bytes)
}
#[cfg(feature = "getrandom")]
#[allow(
clippy::missing_panics_doc,
reason = "`from_bytes` is infallible for Ed25519; the wrapped Result is a forward-compat shim"
)]
pub fn try_random() -> Result<Self, SignError> {
let mut bytes = [0u8; 32];
let fill = getrandom::fill(&mut bytes).map_err(|e| SignError::SigningFailed(e.to_string()));
let out = fill.and_then(|()| Self::from_bytes(&bytes));
bytes.fill(0);
out
}
#[cfg(feature = "getrandom")]
#[must_use]
#[allow(
clippy::expect_used,
reason = "panicking wrapper — callers needing graceful handling use try_random"
)]
pub fn random() -> Self {
Self::try_random().expect("Ed25519Signer::random: entropy source failed")
}
#[must_use]
pub const fn signing_key(&self) -> &SigningKey {
&self.key
}
#[must_use]
pub fn verifying_key(&self) -> VerifyingKey {
self.key.verifying_key()
}
#[must_use]
pub fn public_key_bytes(&self) -> Vec<u8> {
self.key.verifying_key().as_bytes().to_vec()
}
#[must_use]
pub fn public_key_hex(&self) -> String {
hex::encode(self.key.verifying_key().as_bytes())
}
#[must_use]
pub fn sign_raw(&self, message: &[u8]) -> Signature {
self.key.sign(message)
}
#[must_use]
pub fn sign_output(&self, message: &[u8]) -> SignOutput {
SignOutput::Ed25519(self.sign_raw(message).to_bytes())
}
#[must_use]
pub fn sign_output_with_pubkey(&self, message: &[u8]) -> SignOutput {
SignOutput::Ed25519WithPubkey {
signature: self.sign_raw(message).to_bytes(),
public_key: *self.key.verifying_key().as_bytes(),
}
}
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), SignError> {
let sig_bytes: [u8; 64] = signature.try_into().map_err(|_| {
SignError::InvalidSignature(format!(
"expected 64-byte signature, got {}",
signature.len()
))
})?;
let sig = Signature::from_bytes(&sig_bytes);
self.key
.verifying_key()
.verify(message, &sig)
.map_err(|e| SignError::InvalidSignature(e.to_string()))
}
}