use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand_core::{OsRng, RngCore};
use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;
use crate::{codec, Error, Result};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UserId(#[serde(with = "serde_bytes")] pub Vec<u8>);
impl UserId {
pub fn from_pubkey(pk: &VerifyingKey) -> Self {
UserId(codec::sha256(pk.as_bytes()).to_vec())
}
pub fn as_hex(&self) -> String {
hex::encode(&self.0)
}
}
pub struct Identity {
user_id: UserId,
signing: SigningKey,
}
impl std::fmt::Debug for Identity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Identity")
.field("user_id", &self.user_id.as_hex())
.finish()
}
}
impl Identity {
pub fn generate() -> Self {
let mut seed = [0u8; 32];
OsRng.fill_bytes(&mut seed);
let signing = SigningKey::from_bytes(&seed);
let user_id = UserId::from_pubkey(&signing.verifying_key());
Identity { user_id, signing }
}
pub fn user_id(&self) -> &UserId {
&self.user_id
}
pub fn public_key(&self) -> VerifyingKey {
self.signing.verifying_key()
}
pub fn sign_device_binding(&self, device_id: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.user_id.0.len() + device_id.len());
buf.extend_from_slice(&self.user_id.0);
buf.extend_from_slice(device_id);
self.signing.sign(&buf).to_bytes().to_vec()
}
pub fn verify_device_binding(
user_pk: &VerifyingKey,
user_id: &UserId,
device_id: &[u8],
sig: &[u8],
) -> Result<()> {
let sig: [u8; 64] = sig
.try_into()
.map_err(|_| Error::Identity("bad signature length".into()))?;
let signature = Signature::from_bytes(&sig);
let mut buf = Vec::with_capacity(user_id.0.len() + device_id.len());
buf.extend_from_slice(&user_id.0);
buf.extend_from_slice(device_id);
user_pk
.verify(&buf, &signature)
.map_err(|e| Error::Identity(format!("signature verify failed: {e}")))
}
pub fn export(&self) -> Zeroizing<Vec<u8>> {
#[derive(Serialize)]
struct Export<'a> {
v: u8,
#[serde(with = "serde_bytes")]
seed: &'a [u8],
}
let bytes = codec::encode(&Export {
v: 1,
seed: self.signing.as_bytes(),
})
.expect("identity export cannot fail");
Zeroizing::new(bytes)
}
pub fn import(bytes: &[u8]) -> Result<Self> {
#[derive(Deserialize)]
struct Export {
v: u8,
#[serde(with = "serde_bytes")]
seed: Vec<u8>,
}
let imported: Export = codec::decode(bytes)?;
if imported.v != 1 {
return Err(Error::Identity(format!(
"unknown export version {}",
imported.v
)));
}
let seed: [u8; 32] = imported
.seed
.as_slice()
.try_into()
.map_err(|_| Error::Identity("bad seed length".into()))?;
let signing = SigningKey::from_bytes(&seed);
let user_id = UserId::from_pubkey(&signing.verifying_key());
Ok(Identity { user_id, signing })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn export_roundtrip() {
let id = Identity::generate();
let exported = id.export();
let restored = Identity::import(&exported).unwrap();
assert_eq!(id.user_id(), restored.user_id());
assert_eq!(id.public_key().as_bytes(), restored.public_key().as_bytes());
}
#[test]
fn device_binding_verifies() {
let id = Identity::generate();
let device_id = b"device-1";
let sig = id.sign_device_binding(device_id);
Identity::verify_device_binding(&id.public_key(), id.user_id(), device_id, &sig).unwrap();
}
}