did_utils/crypto/
alg.rs

1use num_bigint::{BigInt, Sign};
2
3use crate::{
4    crypto::Error as CryptoError,
5    jwk::{Bytes, Ec, EcCurves, Jwk, Key, Okp, OkpCurves, Parameters},
6};
7
8use multibase::Base::Base58Btc;
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12/// Supported cryptographic algorithms.
13#[derive(Debug, Copy, Clone, PartialEq)]
14#[allow(unused, clippy::upper_case_acronyms)]
15pub enum Algorithm {
16    Ed25519,
17    X25519,
18    Secp256k1,
19    BLS12381,
20    P256,
21    P384,
22    P521,
23    RSA,
24}
25
26use Algorithm::*;
27
28// See:
29// - https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
30// - https://w3c-ccg.github.io/did-method-key/#encryption-method-creation-algorithm
31impl Algorithm {
32    /// Returns the multicodec prefix associated with the algorithm.
33    ///
34    /// # Returns
35    ///
36    /// A two-byte array representing the multicodec prefix.
37    pub fn muticodec_prefix(&self) -> [u8; 2] {
38        match self {
39            Ed25519 => [0xed, 0x01],
40            X25519 => [0xec, 0x01],
41            Secp256k1 => [0xe7, 0x01],
42            BLS12381 => [0xeb, 0x01],
43            P256 => [0x80, 0x24],
44            P384 => [0x81, 0x24],
45            P521 => [0x82, 0x24],
46            RSA => [0x85, 0x24],
47        }
48    }
49
50    /// Creates an `Algorithm` enum variant from the given multicodec prefix.
51    ///
52    /// # Parameters
53    ///
54    /// - `prefix`: A two-byte array representing the multicodec prefix.
55    ///
56    /// # Returns
57    ///
58    /// An `Option` containing the corresponding `Algorithm` variant.
59    pub fn from_muticodec_prefix(prefix: &[u8; 2]) -> Option<Self> {
60        match prefix {
61            [0xed, 0x01] => Some(Ed25519),
62            [0xec, 0x01] => Some(X25519),
63            [0xe7, 0x01] => Some(Secp256k1),
64            [0xeb, 0x01] => Some(BLS12381),
65            [0x80, 0x24] => Some(P256),
66            [0x81, 0x24] => Some(P384),
67            [0x82, 0x24] => Some(P521),
68            [0x85, 0x24] => Some(RSA),
69            _ => None,
70        }
71    }
72
73    /// Returns the length of the public key for the algorithm, if known.
74    ///
75    /// # Returns
76    ///
77    /// An `Option` containing the length of the public key in bytes.
78    pub fn public_key_length(&self) -> Option<usize> {
79        match self {
80            Ed25519 => Some(32),
81            X25519 => Some(32),
82            Secp256k1 => Some(33),
83            BLS12381 => None,
84            P256 => Some(33),
85            P384 => Some(49),
86            P521 => None,
87            RSA => None,
88        }
89    }
90
91    /// Builds a JSON Web Key from raw public key bytes.
92    ///
93    /// # Parameters
94    ///
95    /// - `raw_public_key_bytes`: The raw public key bytes.
96    ///
97    /// # Returns
98    ///
99    /// A `Result` containing the constructed `Jwk` or a `CryptoError`.
100    pub fn build_jwk(&self, raw_public_key_bytes: &[u8]) -> Result<Jwk, CryptoError> {
101        match self {
102            Ed25519 => Ok(Jwk {
103                key: Key::Okp(Okp {
104                    crv: OkpCurves::Ed25519,
105                    x: Bytes::from(raw_public_key_bytes.to_vec()),
106                    d: None,
107                }),
108                prm: Parameters::default(),
109            }),
110            X25519 => Ok(Jwk {
111                key: Key::Okp(Okp {
112                    crv: OkpCurves::X25519,
113                    x: Bytes::from(raw_public_key_bytes.to_vec()),
114                    d: None,
115                }),
116                prm: Parameters::default(),
117            }),
118            Secp256k1 => {
119                let uncompressed = self.uncompress_public_key(raw_public_key_bytes)?;
120                Ok(Jwk {
121                    key: Key::Ec(Ec {
122                        crv: EcCurves::P256K,
123                        d: None,
124                        x: Bytes::from(uncompressed[1..33].to_vec()),
125                        y: Bytes::from(uncompressed[33..].to_vec()),
126                    }),
127                    prm: Parameters::default(),
128                })
129            }
130            P256 => {
131                let uncompressed = self.uncompress_public_key(raw_public_key_bytes)?;
132                Ok(Jwk {
133                    key: Key::Ec(Ec {
134                        crv: EcCurves::P256,
135                        d: None,
136                        x: Bytes::from(uncompressed[1..33].to_vec()),
137                        y: Bytes::from(uncompressed[33..].to_vec()),
138                    }),
139                    prm: Parameters::default(),
140                })
141            }
142            // TODO! Extend implementation to other algorithms
143            _ => Err(CryptoError::Unsupported),
144        }
145    }
146
147    /// Uncompresses a compressed public key.
148    ///
149    /// # Parameters
150    ///
151    /// - `compressed_key_bytes`: The compressed public key bytes.
152    ///
153    /// # Returns
154    ///
155    /// The bytes representing the uncompressed key or a `CryptoError`.
156    pub fn uncompress_public_key(&self, compressed_key_bytes: &[u8]) -> Result<Vec<u8>, CryptoError> {
157        if let Some(required_length) = self.public_key_length() {
158            if required_length != compressed_key_bytes.len() {
159                return Err(CryptoError::InvalidKeyLength);
160            }
161        }
162
163        let sec1_generic = |p: BigInt, a: BigInt, b: BigInt| {
164            let sign_byte = compressed_key_bytes[0];
165            let sign = match sign_byte {
166                0x02 => 0u8,
167                0x03 => 1u8,
168                _ => return Err(CryptoError::InvalidPublicKey),
169            };
170
171            let x = BigInt::from_bytes_be(Sign::Plus, &compressed_key_bytes[1..]);
172            let y_sq = (x.modpow(&BigInt::from(3u32), &p) + &a * &x + &b) % &p;
173            let mut y = y_sq.modpow(&((&p + BigInt::from(1)) / BigInt::from(4)), &p);
174
175            if &y % BigInt::from(2) != (sign % 2).into() {
176                y = &p - &y;
177            }
178
179            let mut z = vec![0x04];
180            z.append(&mut x.to_bytes_be().1);
181            z.append(&mut y.to_bytes_be().1);
182
183            Ok(z)
184        };
185
186        match self {
187            Secp256k1 => {
188                let p = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f";
189                let p = BigInt::from_bytes_be(Sign::Plus, &hex::decode(p).unwrap());
190
191                let a = BigInt::from(0);
192                let b = BigInt::from(7);
193
194                sec1_generic(p, a, b)
195            }
196            P256 => {
197                let p = "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff";
198                let p = BigInt::from_bytes_be(Sign::Plus, &hex::decode(p).unwrap());
199
200                let a = &p - BigInt::from(3);
201
202                let b = "5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b";
203                let b = BigInt::from_bytes_be(Sign::Plus, &hex::decode(b).unwrap());
204
205                sec1_generic(p, a, b)
206            }
207            _ => Err(CryptoError::Unsupported),
208        }
209    }
210}
211
212#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Error)]
213pub(crate) enum DecodeMultikeyError {
214    #[error("error to multibase decode")]
215    MultibaseDecodeError,
216    #[error("not multibase-encoded in Base58")]
217    NotBase58MultibaseEncoded,
218    #[error("assumed multicodec too short")]
219    MulticodecTooShort,
220    #[error("unknown algorithm")]
221    UnknownAlgorithm,
222}
223
224/// Decodes algorithm and key bytes from multibase-encode value
225pub(crate) fn decode_multikey(multikey: &str) -> Result<(Algorithm, Vec<u8>), DecodeMultikeyError> {
226    let (base, multicodec) = multibase::decode(multikey).map_err(|_| DecodeMultikeyError::MultibaseDecodeError)?;
227
228    // Validate decoded multibase value: base
229    if base != Base58Btc {
230        return Err(DecodeMultikeyError::NotBase58MultibaseEncoded);
231    }
232
233    // Validate decoded multibase value: multicodec
234    if multicodec.len() < 2 {
235        return Err(DecodeMultikeyError::MulticodecTooShort);
236    }
237
238    // Partition multicodec value
239    let multicodec_prefix: &[u8; 2] = &multicodec[..2].try_into().unwrap();
240    let raw_public_key_bytes = &multicodec[2..];
241
242    // Derive algorithm from multicodec prefix
243    let alg = Algorithm::from_muticodec_prefix(multicodec_prefix).ok_or(DecodeMultikeyError::UnknownAlgorithm)?;
244
245    // Output
246    Ok((alg, raw_public_key_bytes.to_vec()))
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use multibase::Base::Base64Url;
253    use serde_json::Value;
254
255    #[test]
256    fn test_can_build_secp256k1_jwk() {
257        let (alg, bytes) = decode_multibase_key("zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme");
258        assert_eq!(alg, Secp256k1);
259
260        let uncompressed = alg.uncompress_public_key(&bytes).unwrap();
261        assert_eq!(uncompressed.len(), 65);
262
263        let jwk = alg.build_jwk(&bytes).unwrap();
264        let expected: Value = serde_json::from_str(
265            r#"{
266                "kty": "EC",
267                "crv": "secp256k1",
268                "x": "h0wVx_2iDlOcblulc8E5iEw1EYh5n1RYtLQfeSTyNc0",
269                "y": "O2EATIGbu6DezKFptj5scAIRntgfecanVNXxat1rnwE"
270            }"#,
271        )
272        .unwrap();
273
274        assert_eq!(
275            json_canon::to_string(&jwk).unwrap(),      //
276            json_canon::to_string(&expected).unwrap(), //
277        )
278    }
279
280    #[test]
281    fn test_can_build_p256_jwk() {
282        let (alg, bytes) = decode_multibase_key("zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169");
283        assert_eq!(alg, P256);
284
285        let uncompressed = alg.uncompress_public_key(&bytes).unwrap();
286        assert_eq!(uncompressed.len(), 65);
287
288        let jwk = alg.build_jwk(&bytes).unwrap();
289        let expected: Value = serde_json::from_str(
290            r#"{
291                "kty": "EC",
292                "crv": "P-256",
293                "x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI",
294                "y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU"
295            }"#,
296        )
297        .unwrap();
298
299        assert_eq!(
300            json_canon::to_string(&jwk).unwrap(),      //
301            json_canon::to_string(&expected).unwrap(), //
302        )
303    }
304
305    #[test]
306    fn test_cannot_build_unsupported_jwk() {
307        let multibase_keys = [
308            (
309                BLS12381, //
310                concat!(
311                    "zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAk",
312                    "ERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
313                ),
314            ),
315            (
316                P384, //
317                "z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
318            ),
319            (
320                P521, //
321                "z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
322            ),
323            (
324                RSA, //
325                concat!(
326                    "z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsu",
327                    "VNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC",
328                    "6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6",
329                    "CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMN",
330                    "UYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
331                ),
332            ),
333        ];
334
335        for (expected_alg, multibase_key) in multibase_keys {
336            let (alg, bytes) = decode_multibase_key(multibase_key);
337            assert_eq!(alg, expected_alg);
338            assert!(matches!(alg.build_jwk(&bytes).unwrap_err(), CryptoError::Unsupported));
339        }
340    }
341
342    #[test]
343    fn test_key_decompression_fails_on_invalid_key_length() {
344        let bytes = hex::decode("023d4de48a477e309548a0ed8ceee086d1aaeceb11f0a8e3a0ffb3e5f44602de1800").unwrap();
345        let uncompressed = P256.uncompress_public_key(&bytes);
346        assert!(matches!(uncompressed.unwrap_err(), CryptoError::InvalidKeyLength));
347    }
348
349    #[test]
350    fn test_key_decompression_fails_on_invalid_sign_byte() {
351        let bytes = hex::decode("113d4de48a477e309548a0ed8ceee086d1aaeceb11f0a8e3a0ffb3e5f44602de18").unwrap();
352        let uncompressed = P256.uncompress_public_key(&bytes);
353        assert!(matches!(uncompressed.unwrap_err(), CryptoError::InvalidPublicKey));
354    }
355
356    fn decode_multibase_key(key: &str) -> (Algorithm, Vec<u8>) {
357        let (_, multicodec) = multibase::decode(key).unwrap();
358
359        let prefix: &[u8; 2] = &multicodec[..2].try_into().unwrap();
360        let bytes = &multicodec[2..];
361
362        (Algorithm::from_muticodec_prefix(prefix).unwrap(), bytes.to_vec())
363    }
364
365    #[test]
366    fn test_decode_multikey() {
367        let multikey = "z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp";
368        let (alg, bytes) = decode_multikey(multikey).unwrap();
369        assert_eq!(alg, Algorithm::Ed25519);
370        assert_eq!(bytes, Base64Url.decode("O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik").unwrap());
371
372        let multikey = "z6LSbuUXWSgPfpiDBjUK6E7yiCKMN2eKJsXn5b55ZgqGz6Mr";
373        let (alg, bytes) = decode_multikey(multikey).unwrap();
374        assert_eq!(alg, Algorithm::X25519);
375        assert_eq!(bytes, Base64Url.decode("A2gufB762KKDkbTX0usDbekRJ-_PPBeVhc2gNgjpswU").unwrap());
376    }
377
378    #[test]
379    fn test_decode_multikey_negative_cases() {
380        let cases = [
381            (
382                "z#6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWpd", //
383                DecodeMultikeyError::MultibaseDecodeError,
384            ),
385            (
386                "Z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", //
387                DecodeMultikeyError::NotBase58MultibaseEncoded,
388            ),
389            (
390                "z6", //
391                DecodeMultikeyError::MulticodecTooShort,
392            ),
393            (
394                "z7MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWpd", //
395                DecodeMultikeyError::UnknownAlgorithm,
396            ),
397        ];
398
399        for (multikey, expected_err) in cases {
400            let err = decode_multikey(multikey).unwrap_err();
401            assert_eq!(err, expected_err);
402        }
403    }
404}