Skip to main content

pubky_common/
keys.rs

1use core::fmt;
2use core::ops::{Deref, DerefMut};
3use core::str::FromStr;
4#[cfg(not(target_arch = "wasm32"))]
5use std::{io, path::Path};
6
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9type ParseError = <pkarr::PublicKey as TryFrom<String>>::Error;
10
11fn parse_public_key(value: &str) -> Result<pkarr::PublicKey, ParseError> {
12    let raw = if PublicKey::is_pubky_prefixed(value) {
13        value.strip_prefix("pubky").unwrap_or(value)
14    } else {
15        value
16    };
17    pkarr::PublicKey::try_from(raw.to_string())
18}
19
20/// Wrapper around [`pkarr::Keypair`] that customizes [`PublicKey`] rendering.
21#[derive(Clone)]
22pub struct Keypair(pkarr::Keypair);
23
24impl Keypair {
25    /// Generate a random keypair.
26    #[must_use]
27    pub fn random() -> Self {
28        Self(pkarr::Keypair::random())
29    }
30
31    /// Export the secret bytes used to derive this keypair.
32    #[must_use]
33    pub fn secret(&self) -> [u8; 32] {
34        let mut out = [0u8; 32];
35        out.copy_from_slice(self.0.secret_key().as_ref());
36        out
37    }
38
39    /// Construct a [`Keypair`] from a 32-byte secret.
40    #[must_use]
41    pub fn from_secret(secret: &[u8; 32]) -> Self {
42        Self(pkarr::Keypair::from_secret_key(secret))
43    }
44
45    /// Read a keypair from a pkarr secret key file.
46    #[cfg(not(target_arch = "wasm32"))]
47    pub fn from_secret_key_file(path: &Path) -> Result<Self, io::Error> {
48        pkarr::Keypair::from_secret_key_file(path).map(Self)
49    }
50
51    /// Return the [`PublicKey`] associated with this [`Keypair`].
52    ///
53    /// Display the returned key with `.to_string()` to get the `pubky<z32>` identifier or
54    /// [`PublicKey::z32()`] when you specifically need the bare z-base32 text (e.g. hostnames).
55    #[must_use]
56    pub fn public_key(&self) -> PublicKey {
57        PublicKey(self.0.public_key())
58    }
59
60    /// Borrow the inner [`pkarr::Keypair`].
61    #[must_use]
62    pub const fn as_inner(&self) -> &pkarr::Keypair {
63        &self.0
64    }
65
66    /// Persist the secret key to disk using the pkarr format.
67    #[cfg(not(target_arch = "wasm32"))]
68    pub fn write_secret_key_file(&self, path: &Path) -> Result<(), io::Error> {
69        self.0.write_secret_key_file(path)
70    }
71
72    /// Extract the inner [`pkarr::Keypair`].
73    #[must_use]
74    pub fn into_inner(self) -> pkarr::Keypair {
75        self.0
76    }
77}
78
79impl fmt::Debug for Keypair {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        self.0.fmt(f)
82    }
83}
84
85impl Deref for Keypair {
86    type Target = pkarr::Keypair;
87
88    fn deref(&self) -> &Self::Target {
89        &self.0
90    }
91}
92
93impl DerefMut for Keypair {
94    fn deref_mut(&mut self) -> &mut Self::Target {
95        &mut self.0
96    }
97}
98
99impl From<pkarr::Keypair> for Keypair {
100    fn from(keypair: pkarr::Keypair) -> Self {
101        Self(keypair)
102    }
103}
104
105impl From<Keypair> for pkarr::Keypair {
106    fn from(value: Keypair) -> Self {
107        value.0
108    }
109}
110
111/// Wrapper around [`pkarr::PublicKey`] that renders with the `pubky` prefix.
112///
113/// Note: human-readable serde, transport, and database formats use raw z32 strings. Use
114/// [`PublicKey::z32()`] for hostnames, query parameters, storage, and wire formats.
115#[derive(Clone, PartialEq, Eq, Hash)]
116pub struct PublicKey(pkarr::PublicKey);
117
118impl PublicKey {
119    /// Returns true if the value is in `pubky<z32>` form.
120    pub fn is_pubky_prefixed(value: &str) -> bool {
121        matches!(value.strip_prefix("pubky"), Some(stripped) if stripped.len() == 52)
122    }
123
124    /// Borrow the inner [`pkarr::PublicKey`].
125    #[must_use]
126    pub const fn as_inner(&self) -> &pkarr::PublicKey {
127        &self.0
128    }
129
130    /// Extract the inner [`pkarr::PublicKey`].
131    #[must_use]
132    pub fn into_inner(self) -> pkarr::PublicKey {
133        self.0
134    }
135
136    /// Return the raw z-base32 representation without the `pubky` prefix.
137    ///
138    /// This is the canonical transport/storage form used for hostnames, query
139    /// parameters, serde, and database persistence.
140    #[must_use]
141    pub fn z32(&self) -> String {
142        self.0.to_string()
143    }
144
145    /// Parse a public key from raw z-base32 text (without the `pubky` prefix).
146    pub fn try_from_z32(value: &str) -> Result<Self, ParseError> {
147        pkarr::PublicKey::try_from(value.to_string()).map(Self)
148    }
149}
150
151impl fmt::Display for PublicKey {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(f, "pubky{}", self.z32())
154    }
155}
156
157impl fmt::Debug for PublicKey {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        f.debug_tuple("PublicKey").field(&self.to_string()).finish()
160    }
161}
162
163impl Serialize for PublicKey {
164    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
165        if serializer.is_human_readable() {
166            serializer.serialize_str(&self.z32())
167        } else {
168            self.0.serialize(serializer)
169        }
170    }
171}
172
173impl<'de> Deserialize<'de> for PublicKey {
174    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
175        if deserializer.is_human_readable() {
176            let value = String::deserialize(deserializer)?;
177            Self::try_from_z32(&value).map_err(serde::de::Error::custom)
178        } else {
179            pkarr::PublicKey::deserialize(deserializer).map(Self)
180        }
181    }
182}
183
184impl Deref for PublicKey {
185    type Target = pkarr::PublicKey;
186
187    fn deref(&self) -> &Self::Target {
188        &self.0
189    }
190}
191
192impl From<pkarr::PublicKey> for PublicKey {
193    fn from(value: pkarr::PublicKey) -> Self {
194        Self(value)
195    }
196}
197
198impl From<&pkarr::PublicKey> for PublicKey {
199    fn from(value: &pkarr::PublicKey) -> Self {
200        Self(value.clone())
201    }
202}
203
204impl From<PublicKey> for pkarr::PublicKey {
205    fn from(value: PublicKey) -> Self {
206        value.0
207    }
208}
209
210impl From<&PublicKey> for pkarr::PublicKey {
211    fn from(value: &PublicKey) -> Self {
212        value.0.clone()
213    }
214}
215
216impl TryFrom<&str> for PublicKey {
217    type Error = ParseError;
218
219    fn try_from(value: &str) -> Result<Self, Self::Error> {
220        parse_public_key(value).map(Self)
221    }
222}
223
224impl TryFrom<&String> for PublicKey {
225    type Error = ParseError;
226
227    fn try_from(value: &String) -> Result<Self, Self::Error> {
228        parse_public_key(value).map(Self)
229    }
230}
231
232impl TryFrom<String> for PublicKey {
233    type Error = ParseError;
234
235    fn try_from(value: String) -> Result<Self, Self::Error> {
236        parse_public_key(&value).map(Self)
237    }
238}
239
240impl FromStr for PublicKey {
241    type Err = ParseError;
242
243    fn from_str(s: &str) -> Result<Self, Self::Err> {
244        parse_public_key(s).map(Self)
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn public_key_serializes_as_z32() {
254        let public_key = Keypair::random().public_key();
255
256        let json = serde_json::to_string(&public_key).unwrap();
257
258        assert_eq!(json, format!("\"{}\"", public_key.z32()));
259    }
260
261    #[test]
262    fn public_key_deserializes_from_z32() {
263        let public_key = Keypair::random().public_key();
264        let json = format!("\"{}\"", public_key.z32());
265
266        let parsed: PublicKey = serde_json::from_str(&json).unwrap();
267
268        assert_eq!(parsed, public_key);
269    }
270
271    #[test]
272    fn public_key_display_uses_pubky_prefix() {
273        let public_key = Keypair::random().public_key();
274
275        assert_eq!(public_key.to_string(), format!("pubky{}", public_key.z32()));
276    }
277}