Skip to main content

identity_kit/jose/
multikey.rs

1//! Multikey: a public key as a base58btc multibase string with a multicodec header (Controlled
2//! Identifiers 1.0 §2.2.2), the `publicKeyMultibase` of a verification method and the
3//! method-specific id of `did:key`.
4
5use super::{Jwk, JwkError};
6
7/// The multicodec codes of the public key types this crate reads from and writes to a Multikey.
8///
9/// The codes are the [multicodec table] entries; each key is the raw form the table names.
10///
11/// [multicodec table]: https://github.com/multiformats/multicodec/blob/master/table.csv
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[non_exhaustive]
14pub enum KeyCodec {
15    /// `secp256k1-pub` (`0xe7`): a compressed SEC1 point, 33 bytes.
16    Secp256k1,
17    /// `x25519-pub` (`0xec`): the 32-byte X25519 public key of RFC 7748.
18    X25519,
19    /// `ed25519-pub` (`0xed`): the 32-byte Ed25519 public key of RFC 8032.
20    Ed25519,
21    /// `p256-pub` (`0x1200`): a compressed SEC1 point, 33 bytes.
22    P256,
23    /// `rsa-pub` (`0x1205`): a DER-encoded PKCS #1 `RSAPublicKey` (RFC 8017 Appendix A.1.1).
24    Rsa,
25}
26
27impl KeyCodec {
28    /// The multicodec code.
29    pub const fn code(self) -> u64 {
30        match self {
31            Self::Secp256k1 => 0xe7,
32            Self::X25519 => 0xec,
33            Self::Ed25519 => 0xed,
34            Self::P256 => 0x1200,
35            Self::Rsa => 0x1205,
36        }
37    }
38
39    /// The multicodec name: `ed25519-pub`.
40    pub const fn name(self) -> &'static str {
41        match self {
42            Self::Secp256k1 => "secp256k1-pub",
43            Self::X25519 => "x25519-pub",
44            Self::Ed25519 => "ed25519-pub",
45            Self::P256 => "p256-pub",
46            Self::Rsa => "rsa-pub",
47        }
48    }
49
50    /// The codec with multicodec code `code`, if this crate knows it.
51    pub const fn from_code(code: u64) -> Option<Self> {
52        Some(match code {
53            0xe7 => Self::Secp256k1,
54            0xec => Self::X25519,
55            0xed => Self::Ed25519,
56            0x1200 => Self::P256,
57            0x1205 => Self::Rsa,
58            _ => return None,
59        })
60    }
61
62    /// The byte length of the raw key, when the key type has a fixed one.
63    pub const fn key_len(self) -> Option<usize> {
64        match self {
65            Self::Secp256k1 | Self::P256 => Some(33),
66            Self::X25519 | Self::Ed25519 => Some(32),
67            Self::Rsa => None,
68        }
69    }
70
71    const fn key_name(self) -> &'static str {
72        match self {
73            Self::Secp256k1 => "secp256k1",
74            Self::X25519 => "X25519",
75            Self::Ed25519 => "Ed25519",
76            Self::P256 => "P-256",
77            Self::Rsa => "RSA",
78        }
79    }
80}
81
82impl Jwk {
83    /// The key a Multikey string (`z6Mk…`) encodes.
84    ///
85    /// The string is the base58btc multibase (`z`) of a multicodec header followed by the raw
86    /// key; see [`KeyCodec`] for the key types understood.
87    ///
88    /// # Errors
89    ///
90    /// - [`JwkError::InvalidMultikey`] when the string is not `z` followed by base58btc with a
91    ///   complete multicodec header.
92    /// - [`JwkError::UnsupportedMulticodec`] for a key type this crate does not know, such as
93    ///   P-384 or BLS12-381.
94    /// - [`JwkError::InvalidKeyLength`] when the raw key has the wrong length for its type.
95    /// - [`JwkError::InvalidKey`] when the raw key is not a valid key: a point off the curve, a
96    ///   malformed `RSAPublicKey`.
97    /// - [`JwkError::FeatureDisabled`] for a secp256k1 key without the `secp256k1` feature.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use identity_kit::jose::Jwk;
103    ///
104    /// // The Ed25519 key of the did:key specification's first example.
105    /// let jwk = Jwk::from_multikey("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")?;
106    /// assert_eq!((jwk.kty(), jwk.crv()), ("OKP", Some("Ed25519")));
107    /// assert_eq!(jwk.to_multikey()?, "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK");
108    /// # Ok::<(), identity_kit::jose::JwkError>(())
109    /// ```
110    pub fn from_multikey(multikey: &str) -> Result<Self, JwkError> {
111        let (codec, raw) = decode(multikey)?;
112        if let Some(expected) = codec.key_len()
113            && raw.len() != expected
114        {
115            return Err(JwkError::InvalidKeyLength {
116                key: codec.key_name(),
117                expected,
118                actual: raw.len(),
119            });
120        }
121        match codec {
122            KeyCodec::Ed25519 => Self::from_ed25519_public(&raw),
123            KeyCodec::X25519 => Self::from_x25519_public(&raw),
124            KeyCodec::P256 => Self::from_p256_public(&raw),
125            #[cfg(feature = "secp256k1")]
126            KeyCodec::Secp256k1 => Self::from_secp256k1_public(&raw),
127            #[cfg(not(feature = "secp256k1"))]
128            KeyCodec::Secp256k1 => Err(JwkError::FeatureDisabled {
129                key: "secp256k1",
130                feature: "secp256k1",
131            }),
132            KeyCodec::Rsa => Self::from_pkcs1_der(&raw),
133        }
134    }
135
136    /// This key as a Multikey string: base58btc multibase of the multicodec header and the raw
137    /// public key. EC points are compressed; an RSA key is a PKCS #1 `RSAPublicKey`.
138    ///
139    /// Only the public parameters are encoded.
140    ///
141    /// # Errors
142    ///
143    /// [`JwkError::UnsupportedKeyType`] for a key type without a [`KeyCodec`], and the errors
144    /// of the raw key accessors ([`Jwk::ed25519_public_bytes`] and friends) when the key does not
145    /// decode.
146    pub fn to_multikey(&self) -> Result<String, JwkError> {
147        let (codec, raw) = match (self.kty(), self.crv()) {
148            ("OKP", Some("Ed25519")) => (KeyCodec::Ed25519, self.ed25519_public_bytes()?.to_vec()),
149            ("OKP", Some("X25519")) => (KeyCodec::X25519, self.x25519_public_bytes()?.to_vec()),
150            ("EC", Some("P-256")) => {
151                use p256::elliptic_curve::sec1::ToSec1Point as _;
152                let point = self.p256_public_key()?.to_sec1_point(true);
153                (KeyCodec::P256, point.as_bytes().to_vec())
154            }
155            #[cfg(feature = "secp256k1")]
156            ("EC", Some("secp256k1")) => {
157                use k256::elliptic_curve::sec1::ToSec1Point as _;
158                let point = self.secp256k1_public_key()?.to_sec1_point(true);
159                (KeyCodec::Secp256k1, point.as_bytes().to_vec())
160            }
161            ("RSA", _) => (KeyCodec::Rsa, self.to_pkcs1_der()?),
162            _ => return Err(self.unsupported()),
163        };
164        Ok(encode(codec, &raw))
165    }
166}
167
168/// The Multikey string for `raw` of type `codec`.
169pub(crate) fn encode(codec: KeyCodec, raw: &[u8]) -> String {
170    let mut bytes = varint::encode(codec.code());
171    bytes.extend_from_slice(raw);
172    format!("z{}", bs58::encode(bytes).into_string())
173}
174
175/// The codec and raw key of a Multikey string.
176pub(crate) fn decode(multikey: &str) -> Result<(KeyCodec, Vec<u8>), JwkError> {
177    let Some(base58) = multikey.strip_prefix('z') else {
178        return Err(JwkError::InvalidMultikey(
179            "expected the base58btc multibase prefix `z`".to_owned(),
180        ));
181    };
182    let bytes = bs58::decode(base58)
183        .into_vec()
184        .map_err(|e| JwkError::InvalidMultikey(format!("invalid base58btc: {e}")))?;
185    let (code, header_len) = varint::decode(&bytes)?;
186    let codec = KeyCodec::from_code(code).ok_or(JwkError::UnsupportedMulticodec(code))?;
187    Ok((codec, bytes[header_len..].to_vec()))
188}
189
190/// Unsigned varints (LEB128), the multicodec header encoding.
191mod varint {
192    use crate::jose::JwkError;
193
194    /// The longest varint multicodec allows.
195    const MAX_LEN: usize = 9;
196
197    pub(super) fn encode(mut value: u64) -> Vec<u8> {
198        let mut out = Vec::with_capacity(2);
199        loop {
200            let byte = (value & 0x7f) as u8;
201            value >>= 7;
202            if value == 0 {
203                out.push(byte);
204                return out;
205            }
206            out.push(byte | 0x80);
207        }
208    }
209
210    /// The value and the number of bytes it took; the encoding must be minimal.
211    pub(super) fn decode(bytes: &[u8]) -> Result<(u64, usize), JwkError> {
212        let mut value = 0u64;
213        for (i, &byte) in bytes.iter().enumerate().take(MAX_LEN) {
214            value |= u64::from(byte & 0x7f) << (7 * i);
215            if byte & 0x80 == 0 {
216                if i > 0 && byte == 0 {
217                    return Err(JwkError::InvalidMultikey(
218                        "multicodec header is not minimally encoded".to_owned(),
219                    ));
220                }
221                return Ok((value, i + 1));
222            }
223        }
224        Err(JwkError::InvalidMultikey(
225            "multicodec header is truncated or too long".to_owned(),
226        ))
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn varint_round_trips_multicodec_headers() {
236        // Multicodec table: `ed25519-pub` 0xed, `p256-pub` 0x1200, `rsa-pub` 0x1205.
237        assert_eq!(varint::encode(0xed), [0xed, 0x01]);
238        assert_eq!(varint::encode(0x1200), [0x80, 0x24]);
239        assert_eq!(varint::encode(0x1205), [0x85, 0x24]);
240        assert_eq!(varint::encode(0x7f), [0x7f]);
241        for code in [0u64, 1, 0x7f, 0x80, 0xed, 0x1200, 0x1205, u64::MAX >> 1] {
242            let encoded = varint::encode(code);
243            assert_eq!(varint::decode(&encoded).unwrap(), (code, encoded.len()));
244        }
245        assert!(varint::decode(&[0x80]).is_err(), "truncated");
246        assert!(varint::decode(&[0xed, 0x80, 0x00]).is_err(), "not minimal");
247        assert!(varint::decode(&[0x80; 10]).is_err(), "too long");
248    }
249}