Skip to main content

asimov_id/
key.rs

1// This is free and unencumbered software released into the public domain.
2
3//! ASIMOV public keys.
4
5use crate::KeyError;
6use core::{ops::RangeInclusive, str::FromStr};
7use derive_more::Display;
8
9pub const KEY_LEN_MIN: usize = 1 + 32;
10pub const KEY_LEN_MAX: usize = 1 + 44;
11pub const KEY_LEN: RangeInclusive<usize> = KEY_LEN_MIN..=KEY_LEN_MAX;
12
13#[derive(Clone, Copy, Debug, Default, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
14#[display("Ⓐ{}", bs58::encode(self.0).into_string())]
15#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16#[cfg_attr(feature = "serde", serde(try_from = "String", into = "String"))]
17pub struct Key(pub(crate) [u8; 32]);
18
19impl Key {
20    pub fn as_bytes(&self) -> &[u8] {
21        self.0.as_slice()
22    }
23
24    pub fn into_bytes(self) -> [u8; 32] {
25        self.0
26    }
27}
28
29impl FromStr for Key {
30    type Err = KeyError;
31
32    fn from_str(input: &str) -> Result<Self, Self::Err> {
33        if input.is_empty() {
34            return Err(KeyError::EmptyInput);
35        }
36        if !KEY_LEN.contains(&input.len()) {
37            return Err(KeyError::InvalidLength);
38        }
39        if input.chars().next() != Some('Ⓐ') {
40            return Err(KeyError::InvalidPrefix);
41        }
42        let mut output = [0u8; 32];
43        let count = bs58::decode(&input['Ⓐ'.len_utf8()..])
44            .onto(&mut output)
45            .map_err(|e| KeyError::InvalidEncoding(e))?;
46        if count != output.len() {
47            return Err(KeyError::InvalidLength);
48        }
49        Ok(Self(output))
50    }
51}
52
53impl From<[u8; 32]> for Key {
54    fn from(input: [u8; 32]) -> Self {
55        Self(input)
56    }
57}
58
59impl From<&[u8; 32]> for Key {
60    fn from(input: &[u8; 32]) -> Self {
61        Self(input.clone())
62    }
63}
64
65impl From<&Vec<u8>> for Key {
66    fn from(input: &Vec<u8>) -> Self {
67        let mut bytes = [0u8; 32];
68        let len = bytes.len().min(input.len());
69        bytes[..len].copy_from_slice(&input[..len]);
70        Self(bytes)
71    }
72}
73
74#[cfg(feature = "ed25519-dalek")]
75impl From<&ed25519_dalek::VerifyingKey> for Key {
76    fn from(bytes: &ed25519_dalek::VerifyingKey) -> Self {
77        Self(bytes.as_bytes().clone())
78    }
79}
80
81#[cfg(feature = "iroh")]
82impl From<&iroh::PublicKey> for Key {
83    fn from(bytes: &iroh::PublicKey) -> Self {
84        Self(bytes.as_bytes().clone())
85    }
86}
87
88#[cfg(feature = "p2panda")]
89impl From<&p2panda_core::PublicKey> for Key {
90    fn from(bytes: &p2panda_core::PublicKey) -> Self {
91        Self(bytes.as_bytes().clone())
92    }
93}
94
95impl TryFrom<String> for Key {
96    type Error = KeyError;
97
98    fn try_from(input: String) -> Result<Self, Self::Error> {
99        Self::from_str(&input)
100    }
101}
102
103impl AsRef<[u8]> for Key {
104    fn as_ref(&self) -> &[u8] {
105        self.as_bytes()
106    }
107}
108
109impl Into<String> for Key {
110    fn into(self) -> String {
111        self.to_string()
112    }
113}
114
115#[cfg(feature = "iroh")]
116impl TryInto<iroh::PublicKey> for Key {
117    type Error = iroh::KeyParsingError;
118
119    fn try_into(self) -> Result<iroh::PublicKey, Self::Error> {
120        iroh::PublicKey::from_bytes(&self.into_bytes())
121    }
122}
123
124#[cfg(feature = "p2panda")]
125impl TryInto<p2panda_core::PublicKey> for Key {
126    type Error = p2panda_core::IdentityError;
127
128    fn try_into(self) -> Result<p2panda_core::PublicKey, Self::Error> {
129        p2panda_core::PublicKey::from_bytes(&self.into_bytes())
130    }
131}
132
133#[cfg(feature = "eloquent")]
134impl eloquent::ToSql for Key {
135    fn to_sql(&self) -> Result<String, eloquent::error::EloquentError> {
136        let hex: String = self.0.iter().map(|b| format!("{b:02X}")).collect();
137        Ok(format!("X'{hex}'"))
138    }
139}
140
141#[cfg(feature = "libsql")]
142impl libsql::params::IntoValue for Key {
143    fn into_value(self) -> libsql::Result<libsql::Value> {
144        Ok(libsql::Value::Blob(self.0.to_vec()))
145    }
146}
147
148#[cfg(feature = "rocket")]
149impl<'r> rocket::request::FromParam<'r> for Key {
150    type Error = KeyError;
151
152    fn from_param(input: &'r str) -> Result<Self, Self::Error> {
153        Self::from_str(input)
154    }
155}
156
157#[cfg(feature = "turso")]
158impl turso::IntoValue for Key {
159    fn into_value(self) -> turso::Result<turso::Value> {
160        Ok(turso::Value::Blob(self.0.to_vec()))
161    }
162}