defuse_core/
signature.rs

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}