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: 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}