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: std::iter::once(("contentEncoding", "base58".into()))
182 .map(|(k, v)| (k.to_string(), v))
183 .collect(),
184 metadata: Some(
185 Metadata {
186 examples: [
187 #[cfg(feature = "ed25519")]
188 Self::example_ed25519(),
189 #[cfg(feature = "secp256k1")]
190 Self::example_secp256k1(),
191 #[cfg(feature = "p256")]
192 Self::example_p256(),
193 ]
194 .map(serde_json::to_value)
195 .map(Result::unwrap)
196 .into(),
197 ..Default::default()
198 }
199 .into(),
200 ),
201 ..Default::default()
202 }
203 .into()
204 }
205 }
206
207 impl PublicKey {
208 #[cfg(feature = "ed25519")]
209 pub(super) fn example_ed25519() -> Self {
210 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm"
211 .parse()
212 .unwrap()
213 }
214
215 #[cfg(feature = "secp256k1")]
216 pub(super) fn example_secp256k1() -> Self {
217 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
218 .parse()
219 .unwrap()
220 }
221
222 #[cfg(feature = "p256")]
223 pub(super) fn example_p256() -> Self {
224 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
225 .parse()
226 .unwrap()
227 }
228 }
229};
230
231#[cfg(feature = "near-api-types")]
232const _: () = {
233 use near_api_types::crypto::public_key::{
234 ED25519PublicKey, PublicKey as NearPublicKey, Secp256K1PublicKey,
235 };
236
237 impl From<NearPublicKey> for PublicKey {
238 fn from(pk: NearPublicKey) -> Self {
239 match pk {
240 #[cfg(feature = "ed25519")]
241 NearPublicKey::ED25519(pk) => pk.into(),
242 #[cfg(feature = "secp256k1")]
243 NearPublicKey::SECP256K1(pk) => pk.into(),
244 }
245 }
246 }
247
248 impl From<ED25519PublicKey> for PublicKey {
249 fn from(pk: ED25519PublicKey) -> Self {
250 Self::Ed25519(pk.0)
251 }
252 }
253
254 impl From<Secp256K1PublicKey> for PublicKey {
255 fn from(pk: Secp256K1PublicKey) -> Self {
256 Self::Secp256k1(pk.0)
257 }
258 }
259};
260
261#[cfg(test)]
262mod tests {
263 use near_sdk::AccountIdRef;
264 use rstest::rstest;
265
266 use super::*;
267
268 #[rstest]
269 #[cfg_attr(
270 feature = "ed25519",
271 case(
272 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm",
273 "423df0a6640e9467769c55a573f15b9ee999dc8970048959c72890abf5cc3a8e"
274 )
275 )]
276 #[cfg_attr(
277 feature = "secp256k1",
278 case(
279 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
280 "0xbff77166b39599e54e391156eef7b8191e02be92"
281 )
282 )]
283 #[cfg_attr(
284 feature = "p256",
285 case(
286 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
287 "0x7edf07ede58238026db3f90fc8032633b69b8de5"
288 )
289 )]
290 fn to_implicit_account_id(#[case] pk: &str, #[case] expected: &str) {
291 assert_eq!(
292 pk.parse::<PublicKey>().unwrap().to_implicit_account_id(),
293 AccountIdRef::new_or_panic(expected)
294 );
295 }
296
297 #[rstest]
298 #[cfg_attr(
299 feature = "ed25519",
300 case("ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJ"),
301 case("ed25519:")
302 )]
303 #[cfg_attr(
304 feature = "secp256k1",
305 case("secp256k1:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
306 case("secp256k1:")
307 )]
308 #[cfg_attr(
309 feature = "p256",
310 case("p256:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
311 case("p256:")
312 )]
313 fn parse_invalid_length(#[case] pk: &str) {
314 assert_eq!(pk.parse::<PublicKey>(), Err(ParseCurveError::InvalidLength));
315 }
316}