use crate::{Did, Error, Result};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::rngs::OsRng;
use std::fmt;
pub struct RootKey {
signing_key: SigningKey,
}
impl fmt::Debug for RootKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RootKey")
.field("did", &self.did().to_string())
.finish_non_exhaustive()
}
}
impl RootKey {
pub fn generate() -> Self {
Self {
signing_key: SigningKey::generate(&mut OsRng),
}
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self> {
Ok(Self {
signing_key: SigningKey::from_bytes(bytes),
})
}
pub fn did(&self) -> Did {
Did::new(self.signing_key.verifying_key())
}
pub fn verifying_key(&self) -> VerifyingKey {
self.signing_key.verifying_key()
}
pub fn sign(&self, message: &[u8]) -> Signature {
self.signing_key.sign(message)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.signing_key.to_bytes()
}
}
pub struct SessionKey {
signing_key: SigningKey,
root_did: Did,
}
impl fmt::Debug for SessionKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pubkey_fingerprint = {
let full = self.public_key_base58();
if full.len() > 8 {
format!("{}...", &full[..8])
} else {
full
}
};
f.debug_struct("SessionKey")
.field("root_did", &self.root_did.to_string())
.field("pubkey", &pubkey_fingerprint)
.finish_non_exhaustive()
}
}
impl SessionKey {
pub fn generate(root_did: Did) -> Self {
Self {
signing_key: SigningKey::generate(&mut OsRng),
root_did,
}
}
pub fn root_did(&self) -> &Did {
&self.root_did
}
pub fn verifying_key(&self) -> VerifyingKey {
self.signing_key.verifying_key()
}
pub fn sign(&self, message: &[u8]) -> Signature {
self.signing_key.sign(message)
}
pub fn public_key_base58(&self) -> String {
bs58::encode(self.signing_key.verifying_key().as_bytes()).into_string()
}
}
pub fn verify(public_key: &VerifyingKey, message: &[u8], signature: &Signature) -> Result<()> {
public_key
.verify(message, signature)
.map_err(|_| Error::InvalidSignature)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_key_generation() {
let root = RootKey::generate();
let did = root.did();
assert!(did.to_string().starts_with("did:key:"));
}
#[test]
fn test_sign_verify() {
let root = RootKey::generate();
let message = b"hello world";
let signature = root.sign(message);
verify(&root.verifying_key(), message, &signature).unwrap();
}
#[test]
fn test_session_key() {
let root = RootKey::generate();
let session = SessionKey::generate(root.did());
assert_eq!(session.root_did(), &root.did());
let message = b"session message";
let sig = session.sign(message);
verify(&session.verifying_key(), message, &sig).unwrap();
}
#[test]
fn test_root_key_roundtrip() {
let root = RootKey::generate();
let bytes = root.to_bytes();
let restored = RootKey::from_bytes(&bytes).unwrap();
assert_eq!(root.did(), restored.did());
}
#[test]
fn test_root_key_debug_does_not_leak_secrets() {
let root = RootKey::generate();
let debug_output = format!("{:?}", root);
assert!(debug_output.contains("did:key:"));
assert!(
!debug_output.contains("FieldElement"),
"Debug output should not contain internal crypto field elements"
);
assert!(
!debug_output.contains("EdwardsPoint"),
"Debug output should not contain internal crypto types"
);
assert!(
!debug_output.to_lowercase().contains("secret"),
"Debug output should not reference 'secret'"
);
assert!(
debug_output.contains(".."),
"Debug should indicate hidden fields with .."
);
}
#[test]
fn test_session_key_debug_does_not_leak_secrets() {
let root = RootKey::generate();
let session = SessionKey::generate(root.did());
let debug_output = format!("{:?}", session);
assert!(debug_output.contains("root_did"));
assert!(debug_output.contains("pubkey"));
assert!(
debug_output.contains("...\""),
"Public key should be truncated in debug output"
);
assert!(
!debug_output.contains("FieldElement"),
"Debug output should not contain internal crypto field elements"
);
assert!(
!debug_output.contains("EdwardsPoint"),
"Debug output should not contain internal crypto types"
);
assert!(
!debug_output.to_lowercase().contains("secret"),
"Debug output should not reference 'secret'"
);
}
}