1use core::{
2 fmt::{self, Debug, Display},
3 str::FromStr,
4};
5
6use near_sdk::{
7 AccountId, bs58, near,
8 serde_with::{DeserializeFromStr, SerializeDisplay},
9};
10
11use crate::{CurveType, ParseCurveError, parse::checked_base58_decode_array};
12
13#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
14#[near(serializers = [borsh(use_discriminant = true)])]
15#[derive(
16 Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr,
17)]
18#[serde_with(crate = "::near_sdk::serde_with")]
19#[repr(u8)]
20pub enum PublicKey {
21 #[cfg(feature = "ed25519")]
22 Ed25519(<crate::Ed25519 as crate::Curve>::PublicKey) = 0,
23 #[cfg(feature = "secp256k1")]
24 Secp256k1(<crate::Secp256k1 as crate::Curve>::PublicKey) = 1,
25 #[cfg(feature = "p256")]
26 P256(crate::P256UncompressedPublicKey) = 2,
27}
28
29impl PublicKey {
30 #[inline]
31 pub const fn curve_type(&self) -> CurveType {
32 match self {
33 #[cfg(feature = "ed25519")]
34 Self::Ed25519(_) => CurveType::Ed25519,
35 #[cfg(feature = "secp256k1")]
36 Self::Secp256k1(_) => CurveType::Secp256k1,
37 #[cfg(feature = "p256")]
38 Self::P256(_) => CurveType::P256,
39 }
40 }
41
42 #[inline]
43 const fn data(&self) -> &[u8] {
44 #[allow(clippy::match_same_arms)]
45 match self {
46 #[cfg(feature = "ed25519")]
47 Self::Ed25519(data) => data,
48 #[cfg(feature = "secp256k1")]
49 Self::Secp256k1(data) => data,
50 #[cfg(feature = "p256")]
51 Self::P256(data) => &data.0,
52 }
53 }
54
55 #[inline]
56 pub fn to_implicit_account_id(&self) -> AccountId {
57 match self {
58 #[cfg(feature = "ed25519")]
59 Self::Ed25519(pk) => {
60 hex::encode(pk)
62 }
63 #[cfg(feature = "secp256k1")]
64 Self::Secp256k1(pk) => {
65 format!(
67 "0x{}",
68 hex::encode(&::near_sdk::env::keccak256_array(pk)[12..32])
69 )
70 }
71 #[cfg(feature = "p256")]
72 Self::P256(crate::P256UncompressedPublicKey(pk)) => {
73 format!(
87 "0x{}",
88 hex::encode(
89 &::near_sdk::env::keccak256_array([b"p256".as_slice(), pk].concat())
90 [12..32]
91 )
92 )
93 }
94 }
95 .try_into()
96 .unwrap_or_else(|_| unreachable!())
97 }
98
99 #[cfg(feature = "ed25519")]
100 #[inline]
101 pub fn from_implicit_account_id(account_id: &near_sdk::AccountIdRef) -> Option<Self> {
102 let mut pk = [0; 32];
103 hex::decode_to_slice(account_id.as_str(), &mut pk).ok()?;
105 Some(Self::Ed25519(pk))
106 }
107}
108
109impl Debug for PublicKey {
110 #[inline]
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(
113 f,
114 "{}:{}",
115 self.curve_type(),
116 bs58::encode(self.data()).into_string()
117 )
118 }
119}
120
121impl Display for PublicKey {
122 #[inline]
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 fmt::Debug::fmt(self, f)
125 }
126}
127
128impl FromStr for PublicKey {
129 type Err = ParseCurveError;
130
131 fn from_str(s: &str) -> Result<Self, Self::Err> {
132 let (curve, data) = if let Some((curve, data)) = s.split_once(':') {
133 (
134 curve.parse().map_err(|_| ParseCurveError::WrongCurveType)?,
135 data,
136 )
137 } else {
138 #[cfg(not(feature = "ed25519"))]
139 return Err(ParseCurveError::WrongCurveType);
140
141 #[cfg(feature = "ed25519")]
142 (CurveType::Ed25519, s)
143 };
144
145 match curve {
146 #[cfg(feature = "ed25519")]
147 CurveType::Ed25519 => checked_base58_decode_array(data).map(Self::Ed25519),
148 #[cfg(feature = "secp256k1")]
149 CurveType::Secp256k1 => checked_base58_decode_array(data).map(Self::Secp256k1),
150 #[cfg(feature = "p256")]
151 CurveType::P256 => checked_base58_decode_array(data)
152 .map(crate::P256UncompressedPublicKey)
153 .map(Self::P256),
154 }
155 }
156}
157
158#[cfg(feature = "abi")]
159const _: () = {
160 use near_sdk::{
161 schemars::{
162 JsonSchema,
163 r#gen::SchemaGenerator,
164 schema::{InstanceType, Metadata, Schema, SchemaObject},
165 },
166 serde_json,
167 };
168
169 impl JsonSchema for PublicKey {
170 fn schema_name() -> String {
171 String::schema_name()
172 }
173
174 fn is_referenceable() -> bool {
175 false
176 }
177
178 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
179 SchemaObject {
180 instance_type: Some(InstanceType::String.into()),
181 extensions: [("contentEncoding", "base58".into())]
182 .into_iter()
183 .map(|(k, v)| (k.to_string(), v))
184 .collect(),
185 metadata: Some(
186 Metadata {
187 examples: [
188 #[cfg(feature = "ed25519")]
189 Self::example_ed25519(),
190 #[cfg(feature = "secp256k1")]
191 Self::example_secp256k1(),
192 #[cfg(feature = "p256")]
193 Self::example_p256(),
194 ]
195 .map(serde_json::to_value)
196 .map(Result::unwrap)
197 .into(),
198 ..Default::default()
199 }
200 .into(),
201 ),
202 ..Default::default()
203 }
204 .into()
205 }
206 }
207
208 impl PublicKey {
209 #[cfg(feature = "ed25519")]
210 pub(super) fn example_ed25519() -> Self {
211 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm"
212 .parse()
213 .unwrap()
214 }
215
216 #[cfg(feature = "secp256k1")]
217 pub(super) fn example_secp256k1() -> Self {
218 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
219 .parse()
220 .unwrap()
221 }
222
223 #[cfg(feature = "p256")]
224 pub(super) fn example_p256() -> Self {
225 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
226 .parse()
227 .unwrap()
228 }
229 }
230};
231
232#[cfg(feature = "near-api-types")]
233const _: () = {
234 use near_api_types::crypto::public_key::{
235 ED25519PublicKey, PublicKey as NearPublicKey, Secp256K1PublicKey,
236 };
237
238 impl From<NearPublicKey> for PublicKey {
239 fn from(pk: NearPublicKey) -> Self {
240 match pk {
241 #[cfg(feature = "ed25519")]
242 NearPublicKey::ED25519(pk) => pk.into(),
243 #[cfg(feature = "secp256k1")]
244 NearPublicKey::SECP256K1(pk) => pk.into(),
245 }
246 }
247 }
248
249 impl From<ED25519PublicKey> for PublicKey {
250 fn from(pk: ED25519PublicKey) -> Self {
251 Self::Ed25519(pk.0)
252 }
253 }
254
255 impl From<Secp256K1PublicKey> for PublicKey {
256 fn from(pk: Secp256K1PublicKey) -> Self {
257 Self::Secp256k1(pk.0)
258 }
259 }
260};
261
262#[cfg(test)]
263mod tests {
264 use near_sdk::AccountIdRef;
265 use rstest::rstest;
266
267 use super::*;
268
269 #[rstest]
270 #[cfg_attr(
271 feature = "ed25519",
272 case(
273 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm",
274 "423df0a6640e9467769c55a573f15b9ee999dc8970048959c72890abf5cc3a8e"
275 )
276 )]
277 #[cfg_attr(
278 feature = "secp256k1",
279 case(
280 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
281 "0xbff77166b39599e54e391156eef7b8191e02be92"
282 )
283 )]
284 #[cfg_attr(
285 feature = "p256",
286 case(
287 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
288 "0x7edf07ede58238026db3f90fc8032633b69b8de5"
289 )
290 )]
291 fn to_implicit_account_id(#[case] pk: &str, #[case] expected: &str) {
292 assert_eq!(
293 pk.parse::<PublicKey>().unwrap().to_implicit_account_id(),
294 AccountIdRef::new_or_panic(expected)
295 );
296 }
297
298 #[rstest]
299 #[cfg_attr(
300 feature = "ed25519",
301 case("ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJ"),
302 case("ed25519:")
303 )]
304 #[cfg_attr(
305 feature = "secp256k1",
306 case("secp256k1:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
307 case("secp256k1:")
308 )]
309 #[cfg_attr(
310 feature = "p256",
311 case("p256:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
312 case("p256:")
313 )]
314 fn parse_invalid_length(#[case] pk: &str) {
315 assert_eq!(pk.parse::<PublicKey>(), Err(ParseCurveError::InvalidLength));
316 }
317}