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