defuse_crypto/
signature.rs1use core::{
2 fmt::{self, Debug, Display},
3 str::FromStr,
4};
5
6use near_sdk::{
7 bs58, near,
8 serde_with::{DeserializeFromStr, SerializeDisplay},
9};
10
11#[cfg(feature = "ed25519")]
12use crate::Ed25519;
13#[cfg(feature = "p256")]
14use crate::P256;
15#[cfg(feature = "secp256k1")]
16use crate::Secp256k1;
17
18use crate::{Curve, CurveType, ParseCurveError, parse::checked_base58_decode_array};
19
20#[near(serializers = [borsh(use_discriminant = true)])]
21#[derive(
22 Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, SerializeDisplay, DeserializeFromStr,
23)]
24#[serde_with(crate = "::near_sdk::serde_with")]
25#[repr(u8)]
26pub enum Signature {
27 #[cfg(feature = "ed25519")]
28 Ed25519(<Ed25519 as Curve>::Signature) = 0,
29 #[cfg(feature = "secp256k1")]
30 Secp256k1(<Secp256k1 as Curve>::Signature) = 1,
31 #[cfg(feature = "p256")]
32 P256(<P256 as Curve>::Signature) = 2,
33}
34
35impl Signature {
36 #[inline]
37 pub const fn curve_type(&self) -> CurveType {
38 match self {
39 #[cfg(feature = "ed25519")]
40 Self::Ed25519(_) => CurveType::Ed25519,
41 #[cfg(feature = "secp256k1")]
42 Self::Secp256k1(_) => CurveType::Secp256k1,
43 #[cfg(feature = "p256")]
44 Self::P256(_) => CurveType::P256,
45 }
46 }
47
48 #[inline]
49 const fn data(&self) -> &[u8] {
50 #[allow(clippy::match_same_arms)]
51 match self {
52 #[cfg(feature = "ed25519")]
53 Self::Ed25519(data) => data,
54 #[cfg(feature = "secp256k1")]
55 Self::Secp256k1(data) => data,
56 #[cfg(feature = "p256")]
57 Self::P256(data) => data,
58 }
59 }
60}
61
62impl Debug for Signature {
63 #[inline]
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(
66 f,
67 "{}:{}",
68 self.curve_type(),
69 bs58::encode(self.data()).into_string()
70 )
71 }
72}
73
74impl Display for Signature {
75 #[inline]
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 fmt::Debug::fmt(self, f)
78 }
79}
80
81impl FromStr for Signature {
82 type Err = ParseCurveError;
83
84 fn from_str(s: &str) -> Result<Self, Self::Err> {
85 let (curve, data) = if let Some((curve, data)) = s.split_once(':') {
86 (
87 curve.parse().map_err(|_| ParseCurveError::WrongCurveType)?,
88 data,
89 )
90 } else {
91 #[cfg(not(feature = "ed25519"))]
92 return Err(ParseCurveError::WrongCurveType);
93
94 #[cfg(feature = "ed25519")]
95 (CurveType::Ed25519, s)
96 };
97
98 match curve {
99 #[cfg(feature = "ed25519")]
100 CurveType::Ed25519 => checked_base58_decode_array(data).map(Self::Ed25519),
101 #[cfg(feature = "secp256k1")]
102 CurveType::Secp256k1 => checked_base58_decode_array(data).map(Self::Secp256k1),
103 #[cfg(feature = "p256")]
104 CurveType::P256 => checked_base58_decode_array(data).map(Self::P256),
105 }
106 }
107}
108
109#[cfg(feature = "abi")]
110const _: () = {
111 use near_sdk::{
112 schemars::{
113 JsonSchema,
114 r#gen::SchemaGenerator,
115 schema::{InstanceType, Metadata, Schema, SchemaObject},
116 },
117 serde_json,
118 };
119
120 impl JsonSchema for Signature {
121 fn schema_name() -> String {
122 String::schema_name()
123 }
124
125 fn is_referenceable() -> bool {
126 false
127 }
128
129 fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
130 SchemaObject {
131 instance_type: Some(InstanceType::String.into()),
132 extensions: std::iter::once(("contentEncoding", "base58".into()))
133 .map(|(k, v)| (k.to_string(), v))
134 .collect(),
135 metadata: Some(
136 Metadata {
137 examples: [
138 #[cfg(feature = "ed25519")]
139 Self::example_ed25519(),
140 #[cfg(feature = "secp256k1")]
141 Self::example_secp256k1(),
142 #[cfg(feature = "p256")]
143 Self::example_p256(),
144 ]
145 .map(serde_json::to_value)
146 .map(Result::unwrap)
147 .into(),
148 ..Default::default()
149 }
150 .into(),
151 ),
152 ..Default::default()
153 }
154 .into()
155 }
156 }
157
158 impl Signature {
159 #[cfg(feature = "ed25519")]
160 pub(super) fn example_ed25519() -> Self {
161 "ed25519:DNxoVu7L7sHr9pcHGWQoJtPsrwheB8akht1JxaGpc9hGrpehdycXBMLJg4ph1bQ9bXdfoxJCbbwxj3Bdrda52eF"
162 .parse()
163 .unwrap()
164 }
165
166 #[cfg(feature = "secp256k1")]
167 pub(super) fn example_secp256k1() -> Self {
168 "secp256k1:7huDZxNnibusy6wFkbUBQ9Rqq2VmCKgTWYdJwcPj8VnciHjZKPa41rn5n6WZnMqSUCGRHWMAsMjKGtMVVmpETCeCs"
169 .parse()
170 .unwrap()
171 }
172
173 #[cfg(feature = "p256")]
174 pub(super) fn example_p256() -> Self {
175 "p256:DNxoVu7L7sHr9pcHGWQoJtPsrwheB8akht1JxaGpc9hGrpehdycXBMLJg4ph1bQ9bXdfoxJCbbwxj3Bdrda52eF"
176 .parse()
177 .unwrap()
178 }
179 }
180};
181
182#[cfg(test)]
183mod tests {
184 use rstest::rstest;
185
186 use super::*;
187
188 #[rstest]
189 #[cfg_attr(
190 feature = "ed25519",
191 case(
192 "ed25519:4nrYPT9gQbagzC1c7gSRnSkjZukXqjFxnPVp6wjmH1QgsBB1xzsbHB3piY7eHBnofUVS4WRRHpSfTVaqYq9KM265",
193 )
194 )]
195 #[cfg_attr(
196 feature = "secp256k1",
197 case(
198 "secp256k1:7o3557Aipc2MDtvh3E5ZQet85ZcRsynThmhcVZye9mUD1fcG6PBCerX6BKDGkKf3L31DUSkAtSd9o4kGvc3h4wZJ7",
199 )
200 )]
201 #[cfg_attr(
202 feature = "p256",
203 case(
204 "p256:4skfJSJRVHKjXs2FztBcSnTsbSRMjF3ykFz9hB4kZo486KvRrTpwz54uzQawsKtCdM1BdQR6JdAAZXmHreNXmNBj",
205 )
206 )]
207 fn parse_ok(#[case] sig: &str) {
208 sig.parse::<Signature>().unwrap();
209 }
210
211 #[rstest]
212 #[cfg_attr(
213 feature = "ed25519",
214 case("ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJ"),
215 case("ed25519:")
216 )]
217 #[cfg_attr(
218 feature = "secp256k1",
219 case("secp256k1:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
220 case("secp256k1:")
221 )]
222 #[cfg_attr(
223 feature = "p256",
224 case("p256:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
225 case("p256:")
226 )]
227 fn parse_invalid_length(#[case] sig: &str) {
228 assert_eq!(
229 sig.parse::<Signature>(),
230 Err(ParseCurveError::InvalidLength)
231 );
232 }
233}