defuse_crypto/
signature.rs

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