defuse_crypto/
signature.rs

1use 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: [("contentEncoding", "base58".into())]
133                    .into_iter()
134                    .map(|(k, v)| (k.to_string(), v))
135                    .collect(),
136                metadata: Some(
137                    Metadata {
138                        examples: [
139                            #[cfg(feature = "ed25519")]
140                            Self::example_ed25519(),
141                            #[cfg(feature = "secp256k1")]
142                            Self::example_secp256k1(),
143                            #[cfg(feature = "p256")]
144                            Self::example_p256(),
145                        ]
146                        .map(serde_json::to_value)
147                        .map(Result::unwrap)
148                        .into(),
149                        ..Default::default()
150                    }
151                    .into(),
152                ),
153                ..Default::default()
154            }
155            .into()
156        }
157    }
158
159    impl Signature {
160        #[cfg(feature = "ed25519")]
161        pub(super) fn example_ed25519() -> Self {
162            "ed25519:DNxoVu7L7sHr9pcHGWQoJtPsrwheB8akht1JxaGpc9hGrpehdycXBMLJg4ph1bQ9bXdfoxJCbbwxj3Bdrda52eF"
163                .parse()
164                .unwrap()
165        }
166
167        #[cfg(feature = "secp256k1")]
168        pub(super) fn example_secp256k1() -> Self {
169            "secp256k1:7huDZxNnibusy6wFkbUBQ9Rqq2VmCKgTWYdJwcPj8VnciHjZKPa41rn5n6WZnMqSUCGRHWMAsMjKGtMVVmpETCeCs"
170                .parse()
171                .unwrap()
172        }
173
174        #[cfg(feature = "p256")]
175        pub(super) fn example_p256() -> Self {
176            "p256:DNxoVu7L7sHr9pcHGWQoJtPsrwheB8akht1JxaGpc9hGrpehdycXBMLJg4ph1bQ9bXdfoxJCbbwxj3Bdrda52eF"
177                .parse()
178                .unwrap()
179        }
180    }
181};
182
183#[cfg(test)]
184mod tests {
185    use rstest::rstest;
186
187    use super::*;
188
189    #[rstest]
190    #[cfg_attr(
191        feature = "ed25519",
192        case(
193            "ed25519:4nrYPT9gQbagzC1c7gSRnSkjZukXqjFxnPVp6wjmH1QgsBB1xzsbHB3piY7eHBnofUVS4WRRHpSfTVaqYq9KM265",
194        )
195    )]
196    #[cfg_attr(
197        feature = "secp256k1",
198        case(
199            "secp256k1:7o3557Aipc2MDtvh3E5ZQet85ZcRsynThmhcVZye9mUD1fcG6PBCerX6BKDGkKf3L31DUSkAtSd9o4kGvc3h4wZJ7",
200        )
201    )]
202    #[cfg_attr(
203        feature = "p256",
204        case(
205            "p256:4skfJSJRVHKjXs2FztBcSnTsbSRMjF3ykFz9hB4kZo486KvRrTpwz54uzQawsKtCdM1BdQR6JdAAZXmHreNXmNBj",
206        )
207    )]
208    fn parse_ok(#[case] sig: &str) {
209        sig.parse::<Signature>().unwrap();
210    }
211
212    #[rstest]
213    #[cfg_attr(
214        feature = "ed25519",
215        case("ed25519:5TagutioHgKLh7KZ1VEFBYfgRkPtqnKm9LoMnJMJ"),
216        case("ed25519:")
217    )]
218    #[cfg_attr(
219        feature = "secp256k1",
220        case("secp256k1:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
221        case("secp256k1:")
222    )]
223    #[cfg_attr(
224        feature = "p256",
225        case("p256:p3UPfBR3kWxE2C8wF1855eguaoRvoW6jV5ZXbu3sTTCs"),
226        case("p256:")
227    )]
228    fn parse_invalid_length(#[case] sig: &str) {
229        assert_eq!(
230            sig.parse::<Signature>(),
231            Err(ParseCurveError::InvalidLength)
232        );
233    }
234}