defuse_core/
public_key.rs1use core::{
2 fmt::{self, Debug, Display},
3 str::FromStr,
4};
5
6use defuse_crypto::{
7 Curve, CurveType, Ed25519, P256, P256UncompressedPublicKey, ParseCurveError, Secp256k1,
8 TypedCurve,
9};
10use near_sdk::{AccountId, AccountIdRef, bs58, near};
11use serde_with::{DeserializeFromStr, SerializeDisplay};
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 Ed25519(<Ed25519 as Curve>::PublicKey) = 0,
22 Secp256k1(<Secp256k1 as Curve>::PublicKey) = 1,
23 P256(P256UncompressedPublicKey) = 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.0,
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!(
56 "0x{}",
57 hex::encode(&::near_sdk::env::keccak256_array(pk)[12..32])
58 )
59 }
60 Self::P256(P256UncompressedPublicKey(pk)) => {
61 format!(
75 "0x{}",
76 hex::encode(
77 &::near_sdk::env::keccak256_array([b"p256".as_slice(), pk].concat())
78 [12..32]
79 )
80 )
81 }
82 }
83 .try_into()
84 .unwrap_or_else(|_| unreachable!())
85 }
86
87 #[inline]
88 pub fn from_implicit_account_id(account_id: &AccountIdRef) -> Option<Self> {
89 let mut pk = [0; 32];
90 hex::decode_to_slice(account_id.as_str(), &mut pk).ok()?;
92 Some(Self::Ed25519(pk))
93 }
94}
95
96impl Debug for PublicKey {
97 #[inline]
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(
100 f,
101 "{}:{}",
102 self.curve_type(),
103 bs58::encode(self.data()).into_string()
104 )
105 }
106}
107
108impl Display for PublicKey {
109 #[inline]
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 fmt::Debug::fmt(self, f)
112 }
113}
114
115impl FromStr for PublicKey {
116 type Err = ParseCurveError;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 let (curve, data) = if let Some((curve, data)) = s.split_once(':') {
120 (
121 curve.parse().map_err(|_| ParseCurveError::WrongCurveType)?,
122 data,
123 )
124 } else {
125 (CurveType::Ed25519, s)
126 };
127
128 match curve {
129 CurveType::Ed25519 => Ed25519::parse_base58(data).map(Self::Ed25519),
130 CurveType::Secp256k1 => Secp256k1::parse_base58(data).map(Self::Secp256k1),
131 CurveType::P256 => P256::parse_base58(data)
132 .map(P256UncompressedPublicKey)
133 .map(Self::P256),
134 }
135 }
136}
137
138#[cfg(feature = "abi")]
139const _: () = {
140 use near_sdk::{
141 schemars::{
142 JsonSchema,
143 r#gen::SchemaGenerator,
144 schema::{InstanceType, Metadata, Schema, SchemaObject},
145 },
146 serde_json,
147 };
148
149 impl JsonSchema for PublicKey {
150 fn schema_name() -> String {
151 String::schema_name()
152 }
153
154 fn is_referenceable() -> bool {
155 false
156 }
157
158 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
159 SchemaObject {
160 instance_type: Some(InstanceType::String.into()),
161 extensions: std::iter::once(("contentEncoding", "base58".into()))
162 .map(|(k, v)| (k.to_string(), v))
163 .collect(),
164 metadata: Some(
165 Metadata {
166 examples: [
167 Self::example_ed25519(),
168 Self::example_secp256k1(),
169 Self::example_p256(),
170 ]
171 .map(serde_json::to_value)
172 .map(Result::unwrap)
173 .into(),
174 ..Default::default()
175 }
176 .into(),
177 ),
178 ..Default::default()
179 }
180 .into()
181 }
182 }
183
184 impl PublicKey {
185 pub(super) fn example_ed25519() -> Self {
186 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm"
187 .parse()
188 .unwrap()
189 }
190
191 pub(super) fn example_secp256k1() -> Self {
192 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
193 .parse()
194 .unwrap()
195 }
196
197 pub(super) fn example_p256() -> Self {
198 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
199 .parse()
200 .unwrap()
201 }
202 }
203};
204
205#[cfg(feature = "near-api-types")]
206const _: () = {
207 use near_api_types::crypto::public_key::{
208 ED25519PublicKey, PublicKey as NearPublicKey, Secp256K1PublicKey,
209 };
210
211 impl From<NearPublicKey> for PublicKey {
212 fn from(pk: NearPublicKey) -> Self {
213 match pk {
214 NearPublicKey::ED25519(pk) => pk.into(),
215 NearPublicKey::SECP256K1(pk) => pk.into(),
216 }
217 }
218 }
219
220 impl From<ED25519PublicKey> for PublicKey {
221 fn from(pk: ED25519PublicKey) -> Self {
222 Self::Ed25519(pk.0)
223 }
224 }
225
226 impl From<Secp256K1PublicKey> for PublicKey {
227 fn from(pk: Secp256K1PublicKey) -> Self {
228 Self::Secp256k1(pk.0)
229 }
230 }
231};
232
233#[cfg(test)]
234mod tests {
235 use near_sdk::AccountIdRef;
236 use rstest::rstest;
237
238 use super::*;
239
240 #[rstest]
241 #[case(
242 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm",
243 "423df0a6640e9467769c55a573f15b9ee999dc8970048959c72890abf5cc3a8e"
244 )]
245 #[case(
246 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
247 "0xbff77166b39599e54e391156eef7b8191e02be92"
248 )]
249 #[case(
250 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
251 "0x7edf07ede58238026db3f90fc8032633b69b8de5"
252 )]
253 fn to_implicit_account_id(#[case] pk: &str, #[case] expected: &str) {
254 assert_eq!(
255 pk.parse::<PublicKey>().unwrap().to_implicit_account_id(),
256 AccountIdRef::new_or_panic(expected)
257 );
258 }
259
260 #[rstest]
261 #[case("ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJ")]
262 #[case("ed25519:")]
263 #[case("secp256k1:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs")]
264 #[case("secp256k1:")]
265 #[case("p256:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs")]
266 #[case("p256:")]
267 fn parse_invalid_length(#[case] pk: &str) {
268 assert_eq!(pk.parse::<PublicKey>(), Err(ParseCurveError::InvalidLength));
269 }
270}