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")))]
130mod abi {
131 use super::*;
132
133 use near_sdk::{
134 schemars::{
135 JsonSchema,
136 r#gen::SchemaGenerator,
137 schema::{InstanceType, Metadata, Schema, SchemaObject},
138 },
139 serde_json,
140 };
141
142 impl JsonSchema for PublicKey {
143 fn schema_name() -> String {
144 String::schema_name()
145 }
146
147 fn is_referenceable() -> bool {
148 false
149 }
150
151 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
152 SchemaObject {
153 instance_type: Some(InstanceType::String.into()),
154 extensions: [("contentEncoding", "base58".into())]
155 .into_iter()
156 .map(|(k, v)| (k.to_string(), v))
157 .collect(),
158 metadata: Some(
159 Metadata {
160 examples: [Self::example_ed25519(), Self::example_secp256k1()]
161 .map(serde_json::to_value)
162 .map(Result::unwrap)
163 .into(),
164 ..Default::default()
165 }
166 .into(),
167 ),
168 ..Default::default()
169 }
170 .into()
171 }
172 }
173
174 impl PublicKey {
175 pub(super) fn example_ed25519() -> Self {
176 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm"
177 .parse()
178 .unwrap()
179 }
180
181 pub(super) fn example_secp256k1() -> Self {
182 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3"
183 .parse()
184 .unwrap()
185 }
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use rstest::rstest;
192
193 use super::*;
194
195 #[rstest]
196 #[case(
197 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJugxm",
198 "423df0a6640e9467769c55a573f15b9ee999dc8970048959c72890abf5cc3a8e"
199 )]
200 #[case(
201 "secp256k1:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
202 "0xbff77166b39599e54e391156eef7b8191e02be92"
203 )]
204 #[case(
205 "p256:3aMVMxsoAnHUbweXMtdKaN1uJaNwsfKv7wnc97SDGjXhyK62VyJwhPUPLZefKVthcoUcuWK6cqkSU4M542ipNxS3",
206 "0x7edf07ede58238026db3f90fc8032633b69b8de5"
207 )]
208 fn to_implicit_account_id(#[case] pk: &str, #[case] expected: &str) {
209 assert_eq!(
210 pk.parse::<PublicKey>().unwrap().to_implicit_account_id(),
211 AccountIdRef::new_or_panic(expected)
212 );
213 }
214
215 #[rstest]
216 fn parse_invalid_length(
217 #[values(
218 "ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJ",
219 "ed25519:",
220 "secp256k1:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs",
221 "secp256k1:",
222 "p256:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs",
223 "p256:"
224 )]
225 pk: &str,
226 ) {
227 assert_eq!(pk.parse::<PublicKey>(), Err(ParseCurveError::InvalidLength));
228 }
229}