use ed25519::signature::Signer;
use litl::{impl_debug_as_litl, impl_single_tagged_data_serde, SingleTaggedData, TaggedDataError};
use rand07::rngs::OsRng;
use serde::Serialize;
use serde_derive::{Deserialize, Serialize};
use std::{borrow::Cow, fmt::Debug, hash::Hash, ops::Deref};
use thiserror::Error;
#[derive(Clone, PartialEq, Eq)]
pub enum Signature {
SignatureV1(ed25519::Signature),
}
impl SingleTaggedData for Signature {
const TAG: &'static str = "signatureV1";
fn as_bytes(&self) -> Cow<[u8]> {
match self {
Signature::SignatureV1(sig) => Cow::from(sig.as_ref()),
}
}
fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
where
Self: Sized,
{
let sig_bytes: [u8; 64] = data
.try_into()
.map_err(|err| TaggedDataError::data_error(Into::<SignatureError>::into(err)))?;
Ok(Signature::SignatureV1(ed25519::Signature::from(sig_bytes)))
}
}
impl_single_tagged_data_serde!(Signature);
impl_debug_as_litl!(Signature);
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Signature {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Signature::SignatureV1(sig) => sig.to_bytes().hash(state),
}
}
}
impl Ord for Signature {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(Signature::SignatureV1(sig1), Signature::SignatureV1(sig2)) => {
sig1.as_ref().cmp(sig2.as_ref())
}
}
}
}
impl PartialOrd for Signature {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(PartialEq, Eq, Serialize, Deserialize, Hash, Clone, Ord, PartialOrd)]
pub struct Signed<T> {
pub attested: T,
pub signature: Signature,
}
impl<T: Serialize> Debug for Signed<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&litl::to_string_pretty(&self).unwrap())
}
}
impl<T> Deref for Signed<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.attested
}
}
#[derive(Debug, Error)]
pub enum SignatureError {
#[error(transparent)]
WrongSignature(#[from] ed25519_dalek::SignatureError),
#[error("Invalid Signature length")]
InvalidLength(#[from] std::array::TryFromSliceError),
}
impl<T: Clone + Serialize> Signed<T> {
pub fn ensure_signed_by(&self, id: &SignerID) -> Result<(), SignatureError> {
match (id, &self.signature) {
(SignerID::SignerV1(pub_key), Signature::SignatureV1(signature)) => Ok(pub_key
.verify_strict(&litl::to_vec_canonical(&self.attested).unwrap(), signature)?),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum SignerID {
SignerV1(ed25519_dalek::PublicKey),
}
impl SingleTaggedData for SignerID {
const TAG: &'static str = "signerV1";
fn as_bytes(&self) -> Cow<[u8]> {
match self {
SignerID::SignerV1(pub_key) => Cow::from(pub_key.as_bytes().as_ref()),
}
}
fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
where
Self: Sized,
{
Ok(SignerID::SignerV1(
ed25519_dalek::PublicKey::from_bytes(data)
.map_err(Into::<SignerIDError>::into)
.map_err(TaggedDataError::data_error)?,
))
}
}
impl_single_tagged_data_serde!(SignerID);
impl_debug_as_litl!(SignerID);
#[derive(Debug, Error)]
pub enum SignerIDError {
#[error("Invalid SignedID length {0}")]
InvalidLength(#[from] ed25519_dalek::SignatureError),
}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for SignerID {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
SignerID::SignerV1(pub_key) => pub_key.as_bytes().hash(state),
}
}
}
pub enum SignerSecret {
SignerSecretV1(ed25519_dalek::Keypair),
}
impl SingleTaggedData for SignerSecret {
const TAG: &'static str = "signerSecretV1";
fn as_bytes(&self) -> Cow<[u8]> {
Cow::from(self.to_bytes().to_vec())
}
fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
where
Self: Sized,
{
Ok(SignerSecret::SignerSecretV1(
ed25519_dalek::Keypair::from_bytes(data)
.map_err(Into::<SignerSecretError>::into)
.map_err(TaggedDataError::data_error)?,
))
}
}
impl_single_tagged_data_serde!(SignerSecret);
impl_debug_as_litl!(SignerSecret);
#[derive(Debug, Error)]
pub enum SignerSecretError {
#[error("Invalid SignerSecret length {0}")]
InvalidLength(#[from] ed25519_dalek::SignatureError),
}
impl SignerSecret {
pub fn new_random() -> Self {
SignerSecret::SignerSecretV1(ed25519_dalek::Keypair::generate(&mut OsRng {}))
}
pub fn sign<T: Clone + Serialize>(&self, data: T) -> Signed<T> {
let signature =
Signature::SignatureV1(self.deref().sign(&litl::to_vec_canonical(&data).unwrap()));
Signed {
attested: data,
signature,
}
}
pub fn pub_id(&self) -> SignerID {
match self {
SignerSecret::SignerSecretV1(keypair) => SignerID::SignerV1(keypair.public),
}
}
}
impl Deref for SignerSecret {
type Target = ed25519_dalek::Keypair;
fn deref(&self) -> &Self::Target {
match self {
SignerSecret::SignerSecretV1(keypair) => keypair,
}
}
}
#[cfg(test)]
mod test {
use serde_derive::{Deserialize, Serialize};
use crate::signing::{SignerID, SignerSecret};
#[derive(Clone, Serialize, Deserialize)]
struct TestData {
bla: [u8; 4],
}
#[test]
fn signing_and_verifying_works() {
let data = TestData { bla: [1, 2, 3, 4] };
let signer = SignerSecret::new_random();
let signed = signer.sign(data);
println!("{}", litl::to_string_pretty(&signed).unwrap());
assert!(signed.ensure_signed_by(&signer.pub_id()).is_ok());
}
#[test]
fn signer_id_roundtrips_serialization() {
let signer = SignerSecret::new_random();
let signer_id = signer.pub_id();
let serialized = litl::to_vec(&signer_id).unwrap();
let deserialized: SignerID = litl::from_slice(&serialized).unwrap();
assert_eq!(signer_id, deserialized);
}
#[test]
fn serialization() {
let data = TestData { bla: [1, 2, 3, 4] };
let signer = SignerSecret::new_random();
let _signed = signer.sign(data);
assert!(
litl::to_string(&signer.pub_id())
.unwrap()
.starts_with("\"signerV1_z"),
"{:?}",
litl::to_string(&signer.pub_id()).unwrap()
);
assert!(
litl::to_string(&signer)
.unwrap()
.starts_with("\"signerSecretV1_z"),
"{:?}",
litl::to_string(&signer).unwrap()
);
}
}