defuse_core/
public_key.rs1use core::{
2 fmt::{self, Debug, Display},
3 str::FromStr,
4};
5
6use borsh::{BorshDeserialize, BorshSerialize};
7use defuse_crypto::{
8 ed25519::{Ed25519, Ed25519PublicKey},
9 fmt::{ParseCurveError, TypedCurve, checked_base58_decode_array},
10 p256::{P256, P256UncompressedPublicKey},
11 secp256k1::{Secp256k1, Secp256k1UncompressedPublicKey},
12};
13use defuse_digest::{Digest, sha3::Keccak256};
14use near_sdk::{AccountId, AccountIdRef};
15use serde_with::{DeserializeFromStr, SerializeDisplay};
16
17#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
18#[cfg_attr(feature = "abi", derive(::borsh::BorshSchema))]
19#[derive(
20 Clone,
21 Copy,
22 Hash,
23 PartialEq,
24 Eq,
25 PartialOrd,
26 Ord,
27 SerializeDisplay,
28 DeserializeFromStr,
29 BorshSerialize,
30 BorshDeserialize,
31 derive_more::From,
32)]
33#[borsh(use_discriminant = true)]
34#[repr(u8)]
35pub enum PublicKey {
36 Ed25519(Ed25519PublicKey) = 0,
37 Secp256k1(Secp256k1UncompressedPublicKey) = 1,
38 P256(P256UncompressedPublicKey) = 2,
39}
40
41impl PublicKey {
42 #[inline]
43 pub fn to_implicit_account_id(&self) -> AccountId {
44 match self {
45 Self::Ed25519(pk) => {
46 hex::encode(pk)
48 }
49 Self::Secp256k1(pk) => {
50 format!("0x{}", hex::encode(&Keccak256::digest(pk)[12..32]))
52 }
53 Self::P256(pk) => {
54 format!(
68 "0x{}",
69 hex::encode(
70 &Keccak256::new_with_prefix(b"p256")
71 .chain_update(pk)
72 .finalize()[12..32]
73 )
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(Ed25519PublicKey(pk).into())
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 match self {
97 Self::Ed25519(pk) => pk.to_string(),
98 Self::Secp256k1(pk) => pk.to_string(),
99 Self::P256(pk) => pk.to_string(),
100 }
101 )
102 }
103}
104
105impl Display for PublicKey {
106 #[inline]
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 fmt::Debug::fmt(self, f)
109 }
110}
111
112impl FromStr for PublicKey {
113 type Err = ParseCurveError;
114
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 let (curve, data) = s
117 .split_once(':')
118 .unwrap_or((Ed25519::CURVE_TYPE, s));
120
121 match curve {
122 Ed25519::CURVE_TYPE => checked_base58_decode_array(data)
123 .map(Ed25519PublicKey)
124 .map(Into::into),
125 Secp256k1::CURVE_TYPE => checked_base58_decode_array(data)
126 .map(Secp256k1UncompressedPublicKey)
127 .map(Into::into),
128 P256::CURVE_TYPE => checked_base58_decode_array(data)
129 .map(P256UncompressedPublicKey)
130 .map(Into::into),
131 _ => Err(ParseCurveError::WrongCurveType),
132 }
133 }
134}
135
136#[cfg(feature = "abi")]
137const _: () = {
138 use schemars::{
139 JsonSchema,
140 r#gen::SchemaGenerator,
141 schema::{InstanceType, Metadata, Schema, SchemaObject},
142 };
143
144 impl JsonSchema for PublicKey {
145 fn schema_name() -> String {
146 String::schema_name()
147 }
148
149 fn is_referenceable() -> bool {
150 false
151 }
152
153 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
154 SchemaObject {
155 instance_type: Some(InstanceType::String.into()),
156 extensions: std::iter::once(("contentEncoding", "base58".into()))
157 .map(|(k, v)| (k.to_string(), v))
158 .collect(),
159 metadata: Some(
160 Metadata {
161 examples: [
162 Self::example_ed25519(),
163 Self::example_secp256k1(),
164 Self::example_p256(),
165 ]
166 .map(serde_json::to_value)
167 .map(Result::unwrap)
168 .into(),
169 ..Default::default()
170 }
171 .into(),
172 ),
173 ..Default::default()
174 }
175 .into()
176 }
177 }
178
179 impl PublicKey {
180 pub(super) fn example_ed25519() -> Self {
181 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm"
182 .parse()
183 .unwrap()
184 }
185
186 pub(super) fn example_secp256k1() -> Self {
187 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
188 .parse()
189 .unwrap()
190 }
191
192 pub(super) fn example_p256() -> Self {
193 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
194 .parse()
195 .unwrap()
196 }
197 }
198};
199
200#[cfg(feature = "near-kit")]
201const _: () = {
202 use near_kit::types::PublicKey as NearPublicKey;
203
204 #[allow(clippy::fallible_impl_from)] impl From<NearPublicKey> for PublicKey {
206 #[inline]
207 fn from(pk: NearPublicKey) -> Self {
208 match pk {
209 NearPublicKey::Ed25519(pk) => Self::Ed25519(pk.into()),
210 NearPublicKey::Secp256k1(pk) => Self::Secp256k1(pk.into()),
211 _ => panic!("unsupported public key type"),
212 }
213 }
214 }
215};
216
217#[cfg(test)]
218mod tests {
219 use near_sdk::AccountIdRef;
220 use rstest::rstest;
221
222 use super::*;
223
224 #[rstest]
225 #[case(
226 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm",
227 "423df0a6640e9467769c55a573f15b9ee999dc8970048959c72890abf5cc3a8e"
228 )]
229 #[case(
230 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
231 "0xbff77166b39599e54e391156eef7b8191e02be92"
232 )]
233 #[case(
234 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
235 "0x7edf07ede58238026db3f90fc8032633b69b8de5"
236 )]
237 fn to_implicit_account_id(#[case] pk: &str, #[case] expected: &str) {
238 assert_eq!(
239 pk.parse::<PublicKey>().unwrap().to_implicit_account_id(),
240 AccountIdRef::new_or_panic(expected)
241 );
242 }
243
244 #[rstest]
245 #[case("ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJ")]
246 #[case("ed25519:")]
247 #[case("secp256k1:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs")]
248 #[case("secp256k1:")]
249 #[case("p256:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs")]
250 #[case("p256:")]
251 fn parse_invalid_length(#[case] pk: &str) {
252 assert_eq!(pk.parse::<PublicKey>(), Err(ParseCurveError::InvalidLength));
253 }
254}