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, Serialize};
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: serde/transport/database formats continue to use raw z32 strings. Use
114/// [`PublicKey::z32()`] for hostnames, query parameters, storage, and wire formats.
115#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
116#[serde(transparent)]
117pub struct PublicKey(pkarr::PublicKey);
118
119impl PublicKey {
120    /// Returns true if the value is in `pubky<z32>` form.
121    pub fn is_pubky_prefixed(value: &str) -> bool {
122        matches!(value.strip_prefix("pubky"), Some(stripped) if stripped.len() == 52)
123    }
124
125    /// Borrow the inner [`pkarr::PublicKey`].
126    #[must_use]
127    pub const fn as_inner(&self) -> &pkarr::PublicKey {
128        &self.0
129    }
130
131    /// Extract the inner [`pkarr::PublicKey`].
132    #[must_use]
133    pub fn into_inner(self) -> pkarr::PublicKey {
134        self.0
135    }
136
137    /// Return the raw z-base32 representation without the `pubky` prefix.
138    ///
139    /// This is the canonical transport/storage form used for hostnames, query
140    /// parameters, serde, and database persistence.
141    #[must_use]
142    pub fn z32(&self) -> String {
143        self.0.to_string()
144    }
145
146    /// Parse a public key from raw z-base32 text (without the `pubky` prefix).
147    pub fn try_from_z32(value: &str) -> Result<Self, ParseError> {
148        pkarr::PublicKey::try_from(value.to_string()).map(Self)
149    }
150}
151
152impl fmt::Display for PublicKey {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        write!(f, "pubky{}", self.z32())
155    }
156}
157
158impl fmt::Debug for PublicKey {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        f.debug_tuple("PublicKey").field(&self.to_string()).finish()
161    }
162}
163
164impl Deref for PublicKey {
165    type Target = pkarr::PublicKey;
166
167    fn deref(&self) -> &Self::Target {
168        &self.0
169    }
170}
171
172impl From<pkarr::PublicKey> for PublicKey {
173    fn from(value: pkarr::PublicKey) -> Self {
174        Self(value)
175    }
176}
177
178impl From<&pkarr::PublicKey> for PublicKey {
179    fn from(value: &pkarr::PublicKey) -> Self {
180        Self(value.clone())
181    }
182}
183
184impl From<PublicKey> for pkarr::PublicKey {
185    fn from(value: PublicKey) -> Self {
186        value.0
187    }
188}
189
190impl From<&PublicKey> for pkarr::PublicKey {
191    fn from(value: &PublicKey) -> Self {
192        value.0.clone()
193    }
194}
195
196impl TryFrom<&str> for PublicKey {
197    type Error = ParseError;
198
199    fn try_from(value: &str) -> Result<Self, Self::Error> {
200        parse_public_key(value).map(Self)
201    }
202}
203
204impl TryFrom<&String> for PublicKey {
205    type Error = ParseError;
206
207    fn try_from(value: &String) -> Result<Self, Self::Error> {
208        parse_public_key(value).map(Self)
209    }
210}
211
212impl TryFrom<String> for PublicKey {
213    type Error = ParseError;
214
215    fn try_from(value: String) -> Result<Self, Self::Error> {
216        parse_public_key(&value).map(Self)
217    }
218}
219
220impl FromStr for PublicKey {
221    type Err = ParseError;
222
223    fn from_str(s: &str) -> Result<Self, Self::Err> {
224        parse_public_key(s).map(Self)
225    }
226}