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