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 serde_with::{DeserializeFromStr, SerializeDisplay};
15
16use crate::{AccountId, AccountIdRef};
17
18#[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
19#[cfg_attr(feature = "borsh-schema", derive(::borsh::BorshSchema))]
20#[derive(
21 Clone,
22 Copy,
23 Hash,
24 PartialEq,
25 Eq,
26 PartialOrd,
27 Ord,
28 SerializeDisplay,
29 DeserializeFromStr,
30 BorshSerialize,
31 BorshDeserialize,
32 derive_more::From,
33)]
34#[borsh(use_discriminant = true)]
35#[repr(u8)]
36pub enum PublicKey {
37 Ed25519(Ed25519PublicKey) = 0,
38 Secp256k1(Secp256k1UncompressedPublicKey) = 1,
39 P256(P256UncompressedPublicKey) = 2,
40}
41
42impl PublicKey {
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(&Keccak256::digest(pk)[12..32]))
53 }
54 Self::P256(pk) => {
55 format!(
69 "0x{}",
70 hex::encode(
71 &Keccak256::new_with_prefix(b"p256")
72 .chain_update(pk)
73 .finalize()[12..32]
74 )
75 )
76 }
77 }
78 .try_into()
79 .unwrap_or_else(|_| unreachable!())
80 }
81
82 #[inline]
83 pub fn from_implicit_account_id(account_id: &AccountIdRef) -> Option<Self> {
84 let mut pk = [0; 32];
85 hex::decode_to_slice(account_id.as_str(), &mut pk).ok()?;
87 Some(Ed25519PublicKey(pk).into())
88 }
89}
90
91impl Debug for PublicKey {
92 #[inline]
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(
95 f,
96 "{}",
97 match self {
98 Self::Ed25519(pk) => pk.to_string(),
99 Self::Secp256k1(pk) => pk.to_string(),
100 Self::P256(pk) => pk.to_string(),
101 }
102 )
103 }
104}
105
106impl Display for PublicKey {
107 #[inline]
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 fmt::Debug::fmt(self, f)
110 }
111}
112
113impl FromStr for PublicKey {
114 type Err = ParseCurveError;
115
116 fn from_str(s: &str) -> Result<Self, Self::Err> {
117 let (curve, data) = s
118 .split_once(':')
119 .unwrap_or((Ed25519::CURVE_TYPE, s));
121
122 match curve {
123 Ed25519::CURVE_TYPE => checked_base58_decode_array(data)
124 .map(Ed25519PublicKey)
125 .map(Into::into),
126 Secp256k1::CURVE_TYPE => checked_base58_decode_array(data)
127 .map(Secp256k1UncompressedPublicKey)
128 .map(Into::into),
129 P256::CURVE_TYPE => checked_base58_decode_array(data)
130 .map(P256UncompressedPublicKey)
131 .map(Into::into),
132 _ => Err(ParseCurveError::WrongCurveType),
133 }
134 }
135}
136
137#[cfg(feature = "schemars-v0_8")]
138const _: () = {
139 use schemars::{
140 JsonSchema,
141 r#gen::SchemaGenerator,
142 schema::{InstanceType, Metadata, Schema, SchemaObject},
143 };
144
145 impl JsonSchema for PublicKey {
146 fn schema_name() -> String {
147 String::schema_name()
148 }
149
150 fn is_referenceable() -> bool {
151 false
152 }
153
154 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
155 SchemaObject {
156 instance_type: Some(InstanceType::String.into()),
157 extensions: std::iter::once(("contentEncoding", "base58".into()))
158 .map(|(k, v)| (k.to_string(), v))
159 .collect(),
160 metadata: Some(
161 Metadata {
162 examples: [
163 Self::example_ed25519(),
164 Self::example_secp256k1(),
165 Self::example_p256(),
166 ]
167 .map(serde_json::to_value)
168 .map(Result::unwrap)
169 .into(),
170 ..Default::default()
171 }
172 .into(),
173 ),
174 ..Default::default()
175 }
176 .into()
177 }
178 }
179
180 impl PublicKey {
181 pub(super) fn example_ed25519() -> Self {
182 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm"
183 .parse()
184 .unwrap()
185 }
186
187 pub(super) fn example_secp256k1() -> Self {
188 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
189 .parse()
190 .unwrap()
191 }
192
193 pub(super) fn example_p256() -> Self {
194 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
195 .parse()
196 .unwrap()
197 }
198 }
199};
200
201#[cfg(feature = "near-kit")]
202const _: () = {
203 use near_kit::types::PublicKey as NearPublicKey;
204
205 #[allow(clippy::fallible_impl_from)] impl From<NearPublicKey> for PublicKey {
207 #[inline]
208 fn from(pk: NearPublicKey) -> Self {
209 match pk {
210 NearPublicKey::Ed25519(pk) => Self::Ed25519(pk.into()),
211 NearPublicKey::Secp256k1(pk) => Self::Secp256k1(pk.into()),
212 _ => panic!("unsupported public key type"),
213 }
214 }
215 }
216};
217
218#[cfg(test)]
219mod tests {
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}