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 => {
133                let (n, e) = pkcs1::decode(&raw)?;
134                Self::from_rsa_public(&n, &e)
135            }
136        }
137    }
138
139    /// This key as a Multikey string: base58btc multibase of the multicodec header and the raw
140    /// public key. EC points are compressed; an RSA key is a PKCS #1 `RSAPublicKey`.
141    ///
142    /// Only the public parameters are encoded.
143    ///
144    /// # Errors
145    ///
146    /// [`JwkError::UnsupportedKeyType`] for a key type without a [`KeyCodec`], and the errors
147    /// of the raw key accessors ([`Jwk::ed25519_public_bytes`] and friends) when the key does not
148    /// decode.
149    pub fn to_multikey(&self) -> Result<String, JwkError> {
150        let (codec, raw) = match (self.kty(), self.crv()) {
151            ("OKP", Some("Ed25519")) => (KeyCodec::Ed25519, self.ed25519_public_bytes()?.to_vec()),
152            ("OKP", Some("X25519")) => (KeyCodec::X25519, self.x25519_public_bytes()?.to_vec()),
153            ("EC", Some("P-256")) => {
154                use p256::elliptic_curve::sec1::ToSec1Point as _;
155                let point = self.p256_public_key()?.to_sec1_point(true);
156                (KeyCodec::P256, point.as_bytes().to_vec())
157            }
158            #[cfg(feature = "secp256k1")]
159            ("EC", Some("secp256k1")) => {
160                use k256::elliptic_curve::sec1::ToSec1Point as _;
161                let point = self.secp256k1_public_key()?.to_sec1_point(true);
162                (KeyCodec::Secp256k1, point.as_bytes().to_vec())
163            }
164            ("RSA", _) => {
165                let (n, e) = self.rsa_public_components()?;
166                (KeyCodec::Rsa, pkcs1::encode(&n, &e))
167            }
168            _ => return Err(self.unsupported()),
169        };
170        Ok(encode(codec, &raw))
171    }
172}
173
174/// The Multikey string for `raw` of type `codec`.
175pub(crate) fn encode(codec: KeyCodec, raw: &[u8]) -> String {
176    let mut bytes = varint::encode(codec.code());
177    bytes.extend_from_slice(raw);
178    format!("z{}", bs58::encode(bytes).into_string())
179}
180
181/// The codec and raw key of a Multikey string.
182pub(crate) fn decode(multikey: &str) -> Result<(KeyCodec, Vec<u8>), JwkError> {
183    let Some(base58) = multikey.strip_prefix('z') else {
184        return Err(JwkError::InvalidMultikey(
185            "expected the base58btc multibase prefix `z`".to_owned(),
186        ));
187    };
188    let bytes = bs58::decode(base58)
189        .into_vec()
190        .map_err(|e| JwkError::InvalidMultikey(format!("invalid base58btc: {e}")))?;
191    let (code, header_len) = varint::decode(&bytes)?;
192    let codec = KeyCodec::from_code(code).ok_or(JwkError::UnsupportedMulticodec(code))?;
193    Ok((codec, bytes[header_len..].to_vec()))
194}
195
196/// Unsigned varints (LEB128), the multicodec header encoding.
197mod varint {
198    use crate::jose::JwkError;
199
200    /// The longest varint multicodec allows.
201    const MAX_LEN: usize = 9;
202
203    pub(super) fn encode(mut value: u64) -> Vec<u8> {
204        let mut out = Vec::with_capacity(2);
205        loop {
206            let byte = (value & 0x7f) as u8;
207            value >>= 7;
208            if value == 0 {
209                out.push(byte);
210                return out;
211            }
212            out.push(byte | 0x80);
213        }
214    }
215
216    /// The value and the number of bytes it took; the encoding must be minimal.
217    pub(super) fn decode(bytes: &[u8]) -> Result<(u64, usize), JwkError> {
218        let mut value = 0u64;
219        for (i, &byte) in bytes.iter().enumerate().take(MAX_LEN) {
220            value |= u64::from(byte & 0x7f) << (7 * i);
221            if byte & 0x80 == 0 {
222                if i > 0 && byte == 0 {
223                    return Err(JwkError::InvalidMultikey(
224                        "multicodec header is not minimally encoded".to_owned(),
225                    ));
226                }
227                return Ok((value, i + 1));
228            }
229        }
230        Err(JwkError::InvalidMultikey(
231            "multicodec header is truncated or too long".to_owned(),
232        ))
233    }
234}
235
236/// The DER encoding of PKCS #1 `RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent
237/// INTEGER }` (RFC 8017 Appendix A.1.1), which `rsa-pub` carries.
238mod pkcs1 {
239    use crate::jose::JwkError;
240
241    const SEQUENCE: u8 = 0x30;
242    const INTEGER: u8 = 0x02;
243
244    pub(super) fn encode(n: &[u8], e: &[u8]) -> Vec<u8> {
245        let mut body = integer(n);
246        body.extend(integer(e));
247        tlv(SEQUENCE, &body)
248    }
249
250    /// The big-endian `n` and `e` of `der`, without leading zero octets.
251    pub(super) fn decode(der: &[u8]) -> Result<(Vec<u8>, Vec<u8>), JwkError> {
252        let (body, rest) = read_tlv(der, SEQUENCE)?;
253        if !rest.is_empty() {
254            return Err(invalid("trailing bytes after RSAPublicKey"));
255        }
256        let (n, body) = read_tlv(body, INTEGER)?;
257        let (e, body) = read_tlv(body, INTEGER)?;
258        if !body.is_empty() {
259            return Err(invalid("trailing bytes inside RSAPublicKey"));
260        }
261        Ok((unsigned(n)?.to_vec(), unsigned(e)?.to_vec()))
262    }
263
264    /// A DER INTEGER holding the unsigned big-endian `value`.
265    fn integer(value: &[u8]) -> Vec<u8> {
266        let first = value.iter().position(|&b| b != 0).unwrap_or(value.len());
267        let trimmed = &value[first..];
268        let mut body = Vec::with_capacity(trimmed.len() + 1);
269        if trimmed.first().is_none_or(|&b| b & 0x80 != 0) {
270            body.push(0);
271        }
272        body.extend_from_slice(trimmed);
273        tlv(INTEGER, &body)
274    }
275
276    fn tlv(tag: u8, body: &[u8]) -> Vec<u8> {
277        let mut out = vec![tag];
278        let len = body.len();
279        if len < 0x80 {
280            out.push(len as u8);
281        } else {
282            let be = len.to_be_bytes();
283            let first = be.iter().position(|&b| b != 0).unwrap_or(be.len() - 1);
284            out.push(0x80 | (be.len() - first) as u8);
285            out.extend_from_slice(&be[first..]);
286        }
287        out.extend_from_slice(body);
288        out
289    }
290
291    /// The body of the TLV at the start of `der`, which must have tag `tag`, and what follows.
292    fn read_tlv(der: &[u8], tag: u8) -> Result<(&[u8], &[u8]), JwkError> {
293        let (&found, rest) = der.split_first().ok_or_else(|| invalid("unexpected end"))?;
294        if found != tag {
295            return Err(invalid("unexpected ASN.1 tag"));
296        }
297        let (&first, rest) = rest
298            .split_first()
299            .ok_or_else(|| invalid("unexpected end"))?;
300        let (len, rest) = if first < 0x80 {
301            (usize::from(first), rest)
302        } else {
303            let count = usize::from(first & 0x7f);
304            if count == 0 || count > size_of::<usize>() || rest.len() < count {
305                return Err(invalid("invalid length"));
306            }
307            let (len_bytes, rest) = rest.split_at(count);
308            if len_bytes[0] == 0 || (count == 1 && len_bytes[0] < 0x80) {
309                return Err(invalid("length is not minimally encoded"));
310            }
311            let len = len_bytes
312                .iter()
313                .fold(0usize, |acc, &b| (acc << 8) | usize::from(b));
314            (len, rest)
315        };
316        if rest.len() < len {
317            return Err(invalid("length exceeds input"));
318        }
319        Ok(rest.split_at(len))
320    }
321
322    /// The magnitude of a non-negative, minimally encoded INTEGER body.
323    fn unsigned(body: &[u8]) -> Result<&[u8], JwkError> {
324        match body {
325            [] => Err(invalid("empty INTEGER")),
326            [first, ..] if first & 0x80 != 0 => Err(invalid("negative INTEGER")),
327            [0, second, ..] if second & 0x80 == 0 => Err(invalid("INTEGER has a leading zero")),
328            [0, rest @ ..] => Ok(rest),
329            _ => Ok(body),
330        }
331    }
332
333    fn invalid(reason: &str) -> JwkError {
334        JwkError::InvalidKey {
335            key: "RSA",
336            reason: format!("malformed RSAPublicKey: {reason}"),
337        }
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn varint_round_trips_multicodec_headers() {
347        // Multicodec table: `ed25519-pub` 0xed, `p256-pub` 0x1200, `rsa-pub` 0x1205.
348        assert_eq!(varint::encode(0xed), [0xed, 0x01]);
349        assert_eq!(varint::encode(0x1200), [0x80, 0x24]);
350        assert_eq!(varint::encode(0x1205), [0x85, 0x24]);
351        assert_eq!(varint::encode(0x7f), [0x7f]);
352        for code in [0u64, 1, 0x7f, 0x80, 0xed, 0x1200, 0x1205, u64::MAX >> 1] {
353            let encoded = varint::encode(code);
354            assert_eq!(varint::decode(&encoded).unwrap(), (code, encoded.len()));
355        }
356        assert!(varint::decode(&[0x80]).is_err(), "truncated");
357        assert!(varint::decode(&[0xed, 0x80, 0x00]).is_err(), "not minimal");
358        assert!(varint::decode(&[0x80; 10]).is_err(), "too long");
359    }
360
361    #[test]
362    fn pkcs1_round_trips_and_is_minimal() {
363        // RFC 8017: INTEGERs are two's complement, so a high bit needs a leading zero.
364        let n = [0x80, 0x01];
365        let e = [0x01, 0x00, 0x01];
366        let der = pkcs1::encode(&n, &e);
367        assert_eq!(
368            der,
369            [
370                0x30, 0x0a, 0x02, 0x03, 0x00, 0x80, 0x01, 0x02, 0x03, 0x01, 0x00, 0x01
371            ]
372        );
373        assert_eq!(pkcs1::decode(&der).unwrap(), (n.to_vec(), e.to_vec()));
374        // Leading zeros of the input are dropped.
375        assert_eq!(pkcs1::encode(&[0, 0, 0x80, 0x01], &[0, 1, 0, 1]), der);
376        // Long-form lengths.
377        let big = vec![0x7f; 300];
378        let der = pkcs1::encode(&big, &e);
379        // 1 + 3 + 300 bytes of modulus INTEGER plus 5 of exponent: 309 = 0x135.
380        assert_eq!(&der[..4], [0x30, 0x82, 0x01, 0x35]);
381        assert_eq!(pkcs1::decode(&der).unwrap().0, big);
382        for broken in [
383            &[0x30, 0x00][..],
384            &[0x30, 0x04, 0x02, 0x01, 0xff, 0x02][..],
385            &[0x30, 0x06, 0x02, 0x01, 0x80, 0x02, 0x01, 0x01][..],
386            &[0x30, 0x07, 0x02, 0x02, 0x00, 0x01, 0x02, 0x01, 0x01][..],
387            &[0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00][..],
388            &[0x30, 0x81, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01][..],
389        ] {
390            assert!(pkcs1::decode(broken).is_err(), "{broken:02x?}");
391        }
392    }
393}