key.rs (12521B)
1 //! Canonical public-key and identity identifier value types. 2 //! 3 //! [`PublicKey`] validates a 32-byte x-only secp256k1 public key. [`IdentityId`] 4 //! is a distinct Rust type derived from the same canonical bytes. Both values 5 //! parse exact-width hexadecimal text, preserve fixed-width binary form, and 6 //! emit lowercase hexadecimal text without exposing secret-key or Nostr 7 //! bech32 behavior. 8 9 use crate::Error; 10 11 pub(crate) const IDENTIFIER_BYTE_LENGTH: usize = 32; 12 pub(crate) const IDENTIFIER_HEX_LENGTH: usize = IDENTIFIER_BYTE_LENGTH * 2; 13 14 const HEX_ALPHABET: &[u8; 16] = b"0123456789abcdef"; 15 16 pub(crate) struct EncodedHex([u8; IDENTIFIER_HEX_LENGTH]); 17 18 impl EncodedHex { 19 pub(crate) fn new(bytes: &[u8; IDENTIFIER_BYTE_LENGTH]) -> Self { 20 let mut encoded = [0; IDENTIFIER_HEX_LENGTH]; 21 for (index, byte) in bytes.iter().copied().enumerate() { 22 encoded[index * 2] = HEX_ALPHABET[usize::from(byte >> 4)]; 23 encoded[index * 2 + 1] = HEX_ALPHABET[usize::from(byte & 0x0f)]; 24 } 25 Self(encoded) 26 } 27 28 pub(crate) fn as_str(&self) -> &str { 29 core::str::from_utf8(&self.0).expect("the hexadecimal alphabet is valid UTF-8") 30 } 31 } 32 33 fn decode_nibble(byte: u8, index: usize) -> Result<u8, Error> { 34 match byte { 35 b'0'..=b'9' => Ok(byte - b'0'), 36 b'a'..=b'f' => Ok(byte - b'a' + 10), 37 b'A'..=b'F' => Ok(byte - b'A' + 10), 38 _ => Err(Error::InvalidHexCharacter { index }), 39 } 40 } 41 42 pub(crate) fn parse_hex(value: &str) -> Result<[u8; IDENTIFIER_BYTE_LENGTH], Error> { 43 let encoded = value.as_bytes(); 44 if encoded.len() != IDENTIFIER_HEX_LENGTH { 45 return Err(Error::InvalidHexLength { 46 expected: IDENTIFIER_HEX_LENGTH, 47 actual: encoded.len(), 48 }); 49 } 50 51 let mut bytes = [0; IDENTIFIER_BYTE_LENGTH]; 52 for (index, output) in bytes.iter_mut().enumerate() { 53 let high_index = index * 2; 54 let high = decode_nibble(encoded[high_index], high_index)?; 55 let low = decode_nibble(encoded[high_index + 1], high_index + 1)?; 56 *output = (high << 4) | low; 57 } 58 Ok(bytes) 59 } 60 61 pub(crate) fn validate_public_key_bytes(bytes: &[u8; IDENTIFIER_BYTE_LENGTH]) -> Result<(), Error> { 62 let mut compressed = [0; IDENTIFIER_BYTE_LENGTH + 1]; 63 compressed[0] = 0x02; 64 compressed[1..].copy_from_slice(bytes); 65 k256::PublicKey::from_sec1_bytes(&compressed) 66 .map(|_| ()) 67 .map_err(|_| Error::InvalidPublicKeyBytes) 68 } 69 70 macro_rules! define_identifier { 71 ($(#[$meta:meta])* $visibility:vis struct $name:ident;) => { 72 $(#[$meta])* 73 #[repr(transparent)] 74 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 75 $visibility struct $name([u8; $crate::key::IDENTIFIER_BYTE_LENGTH]); 76 77 impl $name { 78 /// The canonical binary representation length. 79 pub const BYTE_LENGTH: usize = $crate::key::IDENTIFIER_BYTE_LENGTH; 80 81 /// The canonical hexadecimal representation length. 82 pub const HEX_LENGTH: usize = $crate::key::IDENTIFIER_HEX_LENGTH; 83 84 /// Constructs the value from its canonical fixed-width bytes. 85 pub fn from_bytes(bytes: [u8; Self::BYTE_LENGTH]) -> Result<Self, $crate::Error> { 86 $crate::key::validate_public_key_bytes(&bytes)?; 87 Ok(Self::from_validated_bytes(bytes)) 88 } 89 90 pub(crate) const fn from_validated_bytes( 91 bytes: [u8; Self::BYTE_LENGTH], 92 ) -> Self { 93 Self(bytes) 94 } 95 96 /// Parses an exact-width byte slice. 97 pub fn from_slice(bytes: &[u8]) -> Result<Self, $crate::Error> { 98 let bytes: [u8; Self::BYTE_LENGTH] = bytes.try_into().map_err(|_| { 99 $crate::Error::InvalidByteLength { 100 expected: Self::BYTE_LENGTH, 101 actual: bytes.len(), 102 } 103 })?; 104 Self::from_bytes(bytes) 105 } 106 107 /// Parses a 64-character hexadecimal representation. 108 pub fn from_hex(value: &str) -> Result<Self, $crate::Error> { 109 Self::from_bytes($crate::key::parse_hex(value)?) 110 } 111 112 /// Borrows the canonical fixed-width bytes. 113 #[must_use] 114 pub const fn as_bytes(&self) -> &[u8; Self::BYTE_LENGTH] { 115 &self.0 116 } 117 118 /// Returns the canonical fixed-width bytes. 119 #[must_use] 120 pub const fn into_bytes(self) -> [u8; Self::BYTE_LENGTH] { 121 self.0 122 } 123 124 /// Encodes the value as canonical lowercase hexadecimal text. 125 #[must_use] 126 pub fn to_hex(self) -> alloc::string::String { 127 alloc::string::String::from( 128 $crate::key::EncodedHex::new(&self.0).as_str(), 129 ) 130 } 131 } 132 133 impl core::fmt::Debug for $name { 134 fn fmt( 135 &self, 136 formatter: &mut core::fmt::Formatter<'_>, 137 ) -> core::fmt::Result { 138 write!(formatter, "{}(\"{}\")", stringify!($name), self) 139 } 140 } 141 142 impl core::fmt::Display for $name { 143 fn fmt( 144 &self, 145 formatter: &mut core::fmt::Formatter<'_>, 146 ) -> core::fmt::Result { 147 formatter.write_str($crate::key::EncodedHex::new(&self.0).as_str()) 148 } 149 } 150 151 impl core::str::FromStr for $name { 152 type Err = $crate::Error; 153 154 fn from_str(value: &str) -> Result<Self, Self::Err> { 155 Self::from_hex(value) 156 } 157 } 158 159 impl TryFrom<&str> for $name { 160 type Error = $crate::Error; 161 162 fn try_from(value: &str) -> Result<Self, Self::Error> { 163 Self::from_hex(value) 164 } 165 } 166 167 impl TryFrom<alloc::string::String> for $name { 168 type Error = $crate::Error; 169 170 fn try_from(value: alloc::string::String) -> Result<Self, Self::Error> { 171 Self::from_hex(&value) 172 } 173 } 174 175 impl TryFrom<&[u8]> for $name { 176 type Error = $crate::Error; 177 178 fn try_from(value: &[u8]) -> Result<Self, Self::Error> { 179 Self::from_slice(value) 180 } 181 } 182 183 impl TryFrom<[u8; $crate::key::IDENTIFIER_BYTE_LENGTH]> for $name { 184 type Error = $crate::Error; 185 186 fn try_from( 187 value: [u8; $crate::key::IDENTIFIER_BYTE_LENGTH], 188 ) -> Result<Self, Self::Error> { 189 Self::from_bytes(value) 190 } 191 } 192 193 impl AsRef<[u8]> for $name { 194 fn as_ref(&self) -> &[u8] { 195 self.as_bytes() 196 } 197 } 198 199 #[cfg(feature = "serde")] 200 impl serde::Serialize for $name { 201 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 202 where 203 S: serde::Serializer, 204 { 205 serializer.serialize_str($crate::key::EncodedHex::new(&self.0).as_str()) 206 } 207 } 208 209 #[cfg(feature = "serde")] 210 impl<'de> serde::Deserialize<'de> for $name { 211 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 212 where 213 D: serde::Deserializer<'de>, 214 { 215 struct IdentifierVisitor; 216 217 impl serde::de::Visitor<'_> for IdentifierVisitor { 218 type Value = $name; 219 220 fn expecting( 221 &self, 222 formatter: &mut core::fmt::Formatter<'_>, 223 ) -> core::fmt::Result { 224 formatter.write_str(concat!( 225 "a 64-character hexadecimal ", 226 stringify!($name) 227 )) 228 } 229 230 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> 231 where 232 E: serde::de::Error, 233 { 234 $name::from_hex(value).map_err(E::custom) 235 } 236 } 237 238 deserializer.deserialize_str(IdentifierVisitor) 239 } 240 } 241 }; 242 } 243 244 pub(crate) use define_identifier; 245 246 define_identifier! { 247 /// A canonical 32-byte public key. 248 /// 249 /// The key is an explicit byte value and intentionally does not dereference 250 /// to text: 251 /// 252 /// ```compile_fail 253 /// use radroots_identity::PublicKey; 254 /// 255 /// fn accepts_text(_: &str) {} 256 /// let key = PublicKey::from_hex( 257 /// "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df", 258 /// ).unwrap(); 259 /// accepts_text(&key); 260 /// ``` 261 pub struct PublicKey; 262 } 263 264 define_identifier! { 265 /// A canonical public identity identifier. 266 pub struct IdentityId; 267 } 268 269 impl IdentityId { 270 /// Derives the identity identifier from its canonical public key. 271 #[must_use] 272 pub const fn from_public_key(public_key: PublicKey) -> Self { 273 Self::from_validated_bytes(public_key.into_bytes()) 274 } 275 } 276 277 impl From<PublicKey> for IdentityId { 278 fn from(value: PublicKey) -> Self { 279 Self::from_public_key(value) 280 } 281 } 282 283 #[cfg(test)] 284 mod tests { 285 #[cfg(feature = "serde")] 286 use alloc::format; 287 use alloc::string::{String, ToString}; 288 use core::str::FromStr; 289 290 use super::*; 291 292 const ALICE: &str = "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df"; 293 const BOB: &str = "e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af"; 294 295 #[test] 296 fn public_keys_validate_and_canonicalize_hex() { 297 let uppercase = ALICE.to_ascii_uppercase(); 298 let public_key = PublicKey::from_hex(&uppercase).expect("valid fixture public key"); 299 300 assert_eq!(public_key.to_hex(), ALICE); 301 assert_eq!(public_key.to_string(), ALICE); 302 assert_eq!(PublicKey::from_str(ALICE).unwrap(), public_key); 303 assert_eq!(PublicKey::try_from(ALICE).unwrap(), public_key); 304 } 305 306 #[test] 307 fn public_keys_reject_invalid_encodings_and_curve_points() { 308 assert!(matches!( 309 PublicKey::from_hex("00"), 310 Err(Error::InvalidHexLength { 311 expected: PublicKey::HEX_LENGTH, 312 actual: 2, 313 }) 314 )); 315 316 let mut invalid_hex = ALICE.as_bytes().to_vec(); 317 invalid_hex[17] = b'g'; 318 let invalid_hex = String::from_utf8(invalid_hex).expect("ASCII test input"); 319 assert!(matches!( 320 PublicKey::from_hex(&invalid_hex), 321 Err(Error::InvalidHexCharacter { index: 17 }) 322 )); 323 assert!(matches!( 324 PublicKey::from_bytes([0; PublicKey::BYTE_LENGTH]), 325 Err(Error::InvalidPublicKeyBytes) 326 )); 327 assert!(matches!( 328 PublicKey::from_slice(&[0; PublicKey::BYTE_LENGTH - 1]), 329 Err(Error::InvalidByteLength { 330 expected: PublicKey::BYTE_LENGTH, 331 actual, 332 }) if actual == PublicKey::BYTE_LENGTH - 1 333 )); 334 } 335 336 #[test] 337 fn canonical_bytes_round_trip_and_order() { 338 let alice = PublicKey::from_hex(ALICE).expect("alice fixture"); 339 let bob = PublicKey::from_hex(BOB).expect("bob fixture"); 340 341 assert_eq!(PublicKey::from_bytes(alice.into_bytes()).unwrap(), alice); 342 assert_eq!(PublicKey::try_from(alice.as_ref()).unwrap(), alice); 343 assert!(alice < bob); 344 } 345 346 #[test] 347 fn identity_ids_are_distinct_key_derived_values() { 348 let public_key = PublicKey::from_hex(ALICE).expect("valid fixture public key"); 349 let identity_id = IdentityId::from(public_key); 350 351 assert_eq!(identity_id.to_hex(), ALICE); 352 assert_eq!(identity_id.as_bytes(), public_key.as_bytes()); 353 assert_eq!(IdentityId::from_hex(ALICE).unwrap(), identity_id); 354 } 355 356 #[cfg(feature = "serde")] 357 #[test] 358 fn key_values_serde_as_validated_canonical_hex() { 359 let public_key = PublicKey::from_hex(ALICE).expect("valid fixture public key"); 360 let encoded = serde_json::to_string(&public_key).expect("serialize public key"); 361 362 assert_eq!(encoded, format!("\"{ALICE}\"")); 363 assert_eq!( 364 serde_json::from_str::<PublicKey>(&encoded).expect("deserialize public key"), 365 public_key 366 ); 367 assert!(serde_json::from_str::<IdentityId>("\"invalid\"").is_err()); 368 } 369 }