defuse_crypto/
public_key.rs1use core::{
2 fmt::{self, Debug, Display},
3 str::FromStr,
4};
5
6use near_sdk::{AccountId, AccountIdRef, bs58, env, near};
7
8use crate::{Curve, CurveType, Ed25519, P256, ParseCurveError, Secp256k1};
9
10#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
11#[near(serializers = [borsh])]
12#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
13#[cfg_attr(
14 feature = "serde",
15 derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
16)]
17pub enum PublicKey {
18 Ed25519(<Ed25519 as Curve>::PublicKey),
19 Secp256k1(<Secp256k1 as Curve>::PublicKey),
20 P256(<P256 as Curve>::PublicKey),
21}
22
23impl PublicKey {
24 #[inline]
25 pub const fn curve_type(&self) -> CurveType {
26 match self {
27 Self::Ed25519(_) => CurveType::Ed25519,
28 Self::Secp256k1(_) => CurveType::Secp256k1,
29 Self::P256(_) => CurveType::P256,
30 }
31 }
32
33 #[inline]
34 const fn data(&self) -> &[u8] {
35 #[allow(clippy::match_same_arms)]
36 match self {
37 Self::Ed25519(data) => data,
38 Self::Secp256k1(data) => data,
39 Self::P256(data) => data,
40 }
41 }
42
43 #[inline]
44 pub fn to_implicit_account_id(&self) -> AccountId {
45 match self {
46 Self::Ed25519(pk) => {
47 hex::encode(pk)
49 }
50 Self::Secp256k1(pk) => {
51 format!("0x{}", hex::encode(&env::keccak256_array(pk)[12..32]))
53 }
54 Self::P256(pk) => {
55 format!(
69 "0x{}",
70 hex::encode(&env::keccak256_array(&[b"p256".as_slice(), pk].concat())[12..32])
71 )
72 }
73 }
74 .try_into()
75 .unwrap_or_else(|_| unreachable!())
76 }
77
78 #[inline]
79 pub fn from_implicit_account_id(account_id: &AccountIdRef) -> Option<Self> {
80 let mut pk = [0; 32];
81 hex::decode_to_slice(account_id.as_str(), &mut pk).ok()?;
83 Some(Self::Ed25519(pk))
84 }
85}
86
87impl Debug for PublicKey {
88 #[inline]
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 write!(
91 f,
92 "{}:{}",
93 self.curve_type(),
94 bs58::encode(self.data()).into_string()
95 )
96 }
97}
98
99impl Display for PublicKey {
100 #[inline]
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 fmt::Debug::fmt(self, f)
103 }
104}
105
106impl FromStr for PublicKey {
107 type Err = ParseCurveError;
108
109 fn from_str(s: &str) -> Result<Self, Self::Err> {
110 let (curve, data) = if let Some((curve, data)) = s.split_once(':') {
111 (
112 curve.parse().map_err(|_| ParseCurveError::WrongCurveType)?,
113 data,
114 )
115 } else {
116 (CurveType::Ed25519, s)
117 };
118 let decoder = bs58::decode(data.as_bytes());
119 match curve {
120 CurveType::Ed25519 => decoder.into_array_const().map(Self::Ed25519),
121 CurveType::Secp256k1 => decoder.into_array_const().map(Self::Secp256k1),
122 CurveType::P256 => decoder.into_array_const().map(Self::P256),
123 }
124 .map_err(Into::into)
125 }
126}
127
128#[cfg(all(feature = "abi", not(target_arch = "wasm32")))]
129mod abi {
130 use super::*;
131
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:5KN6ZfGZgH1puWwH1Nc1P8xyrFZSPHDw3WUP6iitsjCECJLrGBq"
182 .parse()
183 .unwrap()
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn implicit_ed25519() {
194 assert_eq!(
195 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm"
196 .parse::<PublicKey>()
197 .unwrap()
198 .to_implicit_account_id(),
199 AccountIdRef::new_or_panic(
200 "423df0a6640e9467769c55a573f15b9ee999dc8970048959c72890abf5cc3a8e"
201 )
202 );
203 }
204
205 #[test]
206 fn implicit_secp256k1() {
207 assert_eq!(
208 "secp256k1:5KN6ZfGZgH1puWwH1Nc1P8xyrFZSPHDw3WUP6iitsjCECJLrGBq"
209 .parse::<PublicKey>()
210 .unwrap()
211 .to_implicit_account_id(),
212 AccountIdRef::new_or_panic("0xbff77166b39599e54e391156eef7b8191e02be92")
213 );
214 }
215
216 #[test]
217 fn implicit_p256() {
218 assert_eq!(
219 "p256:5KN6ZfGZgH1puWwH1Nc1P8xyrFZSPHDw3WUP6iitsjCECJLrGBq"
220 .parse::<PublicKey>()
221 .unwrap()
222 .to_implicit_account_id(),
223 AccountIdRef::new_or_panic("0x7edf07ede58238026db3f90fc8032633b69b8de5")
224 );
225 }
226}