nula_core/key/
public_key.rs1use std::fmt;
8use std::str::FromStr;
9
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use thiserror::Error;
12
13use crate::util::hex::{self, HexError};
14
15pub const PUBLIC_KEY_SIZE: usize = 32;
17
18#[derive(Debug, Clone, Copy, Error)]
20#[non_exhaustive]
21pub enum PublicKeyError {
22 #[error("invalid hex encoding: {0}")]
24 Hex(#[from] HexError),
25 #[error("invalid length: expected {PUBLIC_KEY_SIZE} bytes, got {0}")]
27 InvalidLength(usize),
28 #[error("not a valid x-only public key")]
30 InvalidPoint,
31}
32
33#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct PublicKey(secp256k1::XOnlyPublicKey);
50
51impl PublicKey {
52 pub fn from_byte_array(bytes: [u8; PUBLIC_KEY_SIZE]) -> Result<Self, PublicKeyError> {
59 secp256k1::XOnlyPublicKey::from_byte_array(bytes)
60 .map(Self)
61 .map_err(|_| PublicKeyError::InvalidPoint)
62 }
63
64 pub fn from_slice(bytes: &[u8]) -> Result<Self, PublicKeyError> {
71 let array: [u8; PUBLIC_KEY_SIZE] = bytes
72 .try_into()
73 .map_err(|_| PublicKeyError::InvalidLength(bytes.len()))?;
74 Self::from_byte_array(array)
75 }
76
77 pub fn parse<S>(input: S) -> Result<Self, PublicKeyError>
83 where
84 S: AsRef<str>,
85 {
86 let bytes = hex::decode(input.as_ref())?;
87 Self::from_slice(&bytes)
88 }
89
90 #[must_use]
92 pub fn to_byte_array(self) -> [u8; PUBLIC_KEY_SIZE] {
93 self.0.serialize()
94 }
95
96 #[must_use]
98 pub fn to_hex(self) -> String {
99 hex::encode(self.0.serialize())
100 }
101
102 #[must_use]
106 pub const fn as_inner(&self) -> &secp256k1::XOnlyPublicKey {
107 &self.0
108 }
109
110 #[must_use]
121 pub fn verify_schnorr(&self, message: &[u8; 32], sig: &secp256k1::schnorr::Signature) -> bool {
122 secp256k1::SECP256K1
123 .verify_schnorr(sig, message, &self.0)
124 .is_ok()
125 }
126}
127
128impl fmt::Debug for PublicKey {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.debug_tuple("PublicKey").field(&self.to_hex()).finish()
131 }
132}
133
134impl fmt::Display for PublicKey {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 hex::fmt_lower(self.0.serialize(), f)
137 }
138}
139
140impl fmt::LowerHex for PublicKey {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 hex::fmt_lower(self.0.serialize(), f)
143 }
144}
145
146impl FromStr for PublicKey {
147 type Err = PublicKeyError;
148
149 fn from_str(s: &str) -> Result<Self, Self::Err> {
150 Self::parse(s)
151 }
152}
153
154impl From<secp256k1::XOnlyPublicKey> for PublicKey {
155 fn from(value: secp256k1::XOnlyPublicKey) -> Self {
156 Self(value)
157 }
158}
159
160impl Serialize for PublicKey {
161 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162 where
163 S: Serializer,
164 {
165 serializer.collect_str(self)
166 }
167}
168
169impl<'de> Deserialize<'de> for PublicKey {
170 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
171 where
172 D: Deserializer<'de>,
173 {
174 let raw = <&str>::deserialize(deserializer)?;
175 Self::parse(raw).map_err(serde::de::Error::custom)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use hex_literal::hex;
182
183 use super::*;
184
185 const G_X: [u8; 32] = hex!("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798");
187
188 #[test]
189 fn parse_lowercase_hex() {
190 let lower = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
191 let pk = PublicKey::parse(lower).unwrap();
192 assert_eq!(pk.to_hex(), lower);
193 }
194
195 #[test]
196 fn from_byte_array_round_trip() {
197 let pk = PublicKey::from_byte_array(G_X).unwrap();
198 assert_eq!(pk.to_byte_array(), G_X);
199 }
200
201 #[test]
202 fn from_slice_wrong_length() {
203 let err = PublicKey::from_slice(&[0_u8; 16]).unwrap_err();
204 assert!(matches!(err, PublicKeyError::InvalidLength(16)));
205 }
206
207 #[test]
208 fn invalid_point_rejected() {
209 let bytes = hex!("0100000000000000000000000000000000000000000000000000000000000000");
211 let err = PublicKey::from_byte_array(bytes).unwrap_err();
212 assert!(matches!(err, PublicKeyError::InvalidPoint));
213 }
214
215 #[test]
216 fn display_lowercase() {
217 let pk = PublicKey::from_byte_array(G_X).unwrap();
218 let s = format!("{pk}");
219 assert_eq!(s.len(), 64);
220 assert!(
221 s.chars()
222 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
223 );
224 }
225
226 #[test]
227 fn debug_includes_hex() {
228 let pk = PublicKey::from_byte_array(G_X).unwrap();
229 let dbg = format!("{pk:?}");
230 assert!(dbg.contains(&pk.to_hex()));
231 }
232
233 #[test]
234 fn serde_round_trip() {
235 let pk = PublicKey::from_byte_array(G_X).unwrap();
236 let json = serde_json::to_string(&pk).unwrap();
237 let parsed: PublicKey = serde_json::from_str(&json).unwrap();
238 assert_eq!(parsed, pk);
239 }
240
241 #[test]
242 fn ordering_is_lexicographic() {
243 let lhs = PublicKey::from_byte_array(hex!(
244 "0000000000000000000000000000000000000000000000000000000000000002"
245 ))
246 .unwrap();
247 let rhs = PublicKey::from_byte_array(hex!(
248 "0000000000000000000000000000000000000000000000000000000000000003"
249 ))
250 .unwrap();
251 assert!(lhs < rhs);
252 }
253
254 #[test]
255 fn verify_schnorr_round_trip() {
256 use crate::Keys;
257 let keys = Keys::parse("0000000000000000000000000000000000000000000000000000000000000003")
258 .unwrap();
259 let message = hex!("0202020202020202020202020202020202020202020202020202020202020202");
260 let sig = keys.sign_schnorr(&message);
261 assert!(keys.public_key().verify_schnorr(&message, &sig));
263 let mut bad = message;
265 bad[0] ^= 0xff;
266 assert!(!keys.public_key().verify_schnorr(&bad, &sig));
267 }
268}