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