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