ssb_multiformats/
multikey.rs

1//! Implementation of [ssb multikeys](https://spec.scuttlebutt.nz/feed/datatypes.html#multikey).
2use std::cmp::{Eq, Ord, PartialEq, PartialOrd};
3use std::fmt;
4use std::io::{self, Cursor, Write};
5
6use crate::{skip_prefix, split_at_byte};
7use serde::{
8    de::{Deserialize, Deserializer, Error},
9    ser::{Serialize, Serializer},
10};
11
12use ssb_crypto::{AsBytes, Keypair, PublicKey, Signature};
13
14/// A multikey that owns its data.
15#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
16pub enum Multikey {
17    // An [ed25519](http://ed25519.cr.yp.to/) public key.
18    Ed25519(PublicKey),
19}
20
21impl Multikey {
22    /// Take an ed25519 public key and turn it into an opaque `Multikey`.
23    pub fn from_ed25519(pk: &[u8; 32]) -> Multikey {
24        Multikey::Ed25519(PublicKey::from_slice(pk).unwrap())
25    }
26
27    pub fn from_ed25519_slice(pk: &[u8]) -> Multikey {
28        Multikey::Ed25519(PublicKey::from_slice(pk).unwrap())
29    }
30
31    pub fn into_ed25519_public_key(self) -> Option<PublicKey> {
32        match self {
33            Multikey::Ed25519(pk) => Some(pk),
34        }
35    }
36
37    /// Parses a
38    /// [legacy encoding](https://spec.scuttlebutt.nz/feed/datatypes.html#multikey-legacy-encoding)
39    /// into a `Multikey`, also returning the remaining input on success.
40    pub fn from_legacy(mut s: &[u8]) -> Result<(Multikey, &[u8]), DecodeLegacyError> {
41        s = skip_prefix(s, b"@").ok_or(DecodeLegacyError::Sigil)?;
42
43        let (data, suffix) = split_at_byte(s, 0x2E).ok_or(DecodeLegacyError::NoDot)?;
44
45        let tail = skip_prefix(suffix, ED25519_SUFFIX).ok_or(DecodeLegacyError::UnknownSuffix)?;
46
47        if data.len() != ED25519_PK_BASE64_LEN {
48            return Err(DecodeLegacyError::Ed25519WrongSize);
49        }
50
51        if data[ED25519_PK_BASE64_LEN - 2] == b"="[0] {
52            return Err(DecodeLegacyError::Ed25519WrongSize);
53        }
54
55        if data[ED25519_PK_BASE64_LEN - 1] != b"="[0] {
56            return Err(DecodeLegacyError::Ed25519WrongSize);
57        }
58
59        let mut dec_data = [0_u8; 32];
60
61        base64::decode_config_slice(data, base64::STANDARD, &mut dec_data)
62            .map_err(|_| DecodeLegacyError::InvalidBase64)
63            .map(|_| (Multikey::from_ed25519(&dec_data), tail))
64    }
65
66    /// Serialize a `Multikey` into a writer, using the
67    /// [legacy encoding](https://spec.scuttlebutt.nz/feed/datatypes.html#multikey-legacy-encoding).
68    pub fn to_legacy<W: Write>(&self, w: &mut W) -> Result<(), io::Error> {
69        match self {
70            Multikey::Ed25519(ref pk) => {
71                w.write_all(b"@")?;
72
73                let data = pk.as_base64();
74                w.write_all(data.as_bytes())?;
75
76                w.write_all(b".")?;
77                w.write_all(ED25519_SUFFIX)
78            }
79        }
80    }
81
82    /// Serialize a `Multikey` into an owned byte vector, using the
83    /// [legacy encoding](https://spec.scuttlebutt.nz/feed/datatypes.html#multikey-legacy-encoding).
84    pub fn to_legacy_vec(&self) -> Vec<u8> {
85        let mut data = vec![];
86        self.to_legacy(&mut data).unwrap();
87        data
88    }
89
90    /// Serialize a `Multikey` into an owned string, using the
91    /// [legacy encoding](https://spec.scuttlebutt.nz/feed/datatypes.html#multikey-legacy-encoding).
92    pub fn to_legacy_string(&self) -> String {
93        String::from_utf8(self.to_legacy_vec()).unwrap()
94    }
95
96    /// Check whether the given signature of the given text was created by this key.
97    pub fn is_signature_correct(&self, data: &[u8], sig: &Multisig) -> bool {
98        match (&self, &sig.0) {
99            (Multikey::Ed25519(ref pk), _Multisig::Ed25519(ref sig)) => pk.verify(sig, data),
100        }
101    }
102}
103
104impl Serialize for Multikey {
105    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
106    where
107        S: Serializer,
108    {
109        serializer.serialize_str(&self.to_legacy_string())
110    }
111}
112
113impl<'de> Deserialize<'de> for Multikey {
114    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
115    where
116        D: Deserializer<'de>,
117    {
118        let s = String::deserialize(deserializer)?;
119        Multikey::from_legacy(s.as_bytes())
120            .map(|(mk, _)| mk)
121            .map_err(|err| D::Error::custom(format!("Invalid multikey: {}", err)))
122    }
123}
124
125/// Everything that can go wrong when decoding a `Multikey` from the legacy encoding.
126#[derive(Debug, PartialEq, Eq, Clone)]
127pub enum DecodeLegacyError {
128    /// Input did not start with the `"@"` sigil.
129    Sigil,
130    /// Input did not contain a `"."` to separate the data from the suffix.
131    NoDot,
132    /// Invalid utf8 string.
133    InvalidUTF8,
134    /// The base64 portion of the key was invalid.
135    InvalidBase64,
136    /// The suffix is not known to this ssb implementation.
137    UnknownSuffix,
138    /// The suffix declares an ed25519 key, but the data length does not match.
139    Ed25519WrongSize,
140}
141
142impl fmt::Display for DecodeLegacyError {
143    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144        match self {
145            DecodeLegacyError::Sigil => write!(f, "Invalid sigil"),
146            DecodeLegacyError::InvalidUTF8 => write!(f, "Invalid utf8"),
147            DecodeLegacyError::InvalidBase64 => write!(f, "Invalid base64"),
148            DecodeLegacyError::NoDot => write!(f, "No dot"),
149            DecodeLegacyError::UnknownSuffix => write!(f, "Unknown suffix"),
150            DecodeLegacyError::Ed25519WrongSize => write!(f, "Data of wrong length"),
151        }
152    }
153}
154
155impl std::error::Error for DecodeLegacyError {}
156
157/// The secret counterpart to Multikey.
158#[derive(Debug, Clone)]
159pub struct Multisecret(Keypair);
160
161impl Multisecret {
162    /// Parses a
163    /// [legacy encoding](https://spec.scuttlebutt.nz/feed/datatypes.html#multikey-legacy-encoding)
164    /// into a `Multisecret`, also returning the remaining input on success.
165    pub fn from_legacy(s: &[u8]) -> Result<(Multisecret, &[u8]), DecodeLegacyError> {
166        let (data, suffix) = split_at_byte(s, 0x2E).ok_or(DecodeLegacyError::NoDot)?;
167
168        let tail = skip_prefix(suffix, ED25519_SUFFIX).ok_or(DecodeLegacyError::UnknownSuffix)?;
169
170        let data_str = std::str::from_utf8(data).map_err(|_| DecodeLegacyError::InvalidUTF8)?;
171
172        let key_pair = Keypair::from_base64(data_str).ok_or(DecodeLegacyError::InvalidBase64)?;
173
174        Ok((Multisecret(key_pair), tail))
175    }
176
177    /// Serialize a `Multisecret` into a writer, using the
178    /// [legacy encoding](https://spec.scuttlebutt.nz/feed/datatypes.html#multikey-legacy-encoding).
179    pub fn to_legacy<W: Write>(&self, w: &mut W) -> Result<(), io::Error> {
180        let data = self.0.as_base64();
181        w.write_all(data.as_bytes())?;
182        w.write_all(b".")?;
183        w.write_all(ED25519_SUFFIX)
184    }
185}
186
187impl Serialize for Multisecret {
188    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189    where
190        S: Serializer,
191    {
192        let mut s = [0_u8; SSB_ED25519_SECRET_ENCODED_LEN];
193        self.to_legacy(&mut Cursor::new(&mut s[..])).unwrap();
194        serializer.serialize_str(std::str::from_utf8(&s).unwrap())
195    }
196}
197
198impl<'de> Deserialize<'de> for Multisecret {
199    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
200    where
201        D: Deserializer<'de>,
202    {
203        let s = String::deserialize(deserializer)?;
204        Multisecret::from_legacy(s.as_bytes())
205            .map(|(mk, _)| mk)
206            .map_err(|err| D::Error::custom(format!("Invalid multikey: {}", err)))
207    }
208}
209
210/// A signature that owns its data.
211#[derive(Debug, PartialEq, Eq, Clone)]
212pub struct Multisig(_Multisig);
213
214#[derive(Clone)]
215enum _Multisig {
216    // An [ed25519](http://ed25519.cr.yp.to/) signature.
217    Ed25519(Signature),
218}
219
220impl fmt::Debug for _Multisig {
221    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222        match self {
223            _Multisig::Ed25519(data) => write!(f, "Ed25519 signature: {:?}", data.as_bytes()),
224        }
225    }
226}
227
228impl PartialEq for _Multisig {
229    fn eq(&self, other: &_Multisig) -> bool {
230        match (self, other) {
231            (_Multisig::Ed25519(ref a), _Multisig::Ed25519(ref b)) => a.as_bytes() == b.as_bytes(),
232        }
233    }
234}
235
236impl Eq for _Multisig {}
237
238impl Multikey {
239    /// Deserialize a legacy signature corresponding to this key type.
240    pub fn sig_from_legacy<'a>(
241        &self,
242        s: &'a [u8],
243    ) -> Result<(Multisig, &'a [u8]), DecodeSignatureError> {
244        let (data, suffix) = split_at_byte(s, 0x2E).ok_or(DecodeSignatureError::NoDot)?;
245
246        let suffix = skip_prefix(suffix, b"sig").ok_or(DecodeSignatureError::NoDotSig)?;
247
248        match self {
249            Multikey::Ed25519(_) => {
250                let tail =
251                    skip_prefix(suffix, b".ed25519").ok_or(DecodeSignatureError::UnknownSuffix)?;
252
253                if data.len() != ED25519_SIG_BASE64_LEN {
254                    return Err(DecodeSignatureError::Ed25519WrongSize);
255                }
256
257                if data[ED25519_SIG_BASE64_LEN - 2] != b"="[0] {
258                    return Err(DecodeSignatureError::Ed25519WrongSize);
259                }
260
261                let mut dec_data = [0_u8; 64];
262
263                base64::decode_config_slice(data, base64::STANDARD, &mut dec_data[..])
264                    .map_err(DecodeSignatureError::InvalidBase64)
265                    .map(|_| (Multisig::from_ed25519(&dec_data), tail))
266            }
267        }
268    }
269}
270
271impl Multisig {
272    /// Take an ed25519 signature and turn it into an opaque `Multisig`.
273    pub fn from_ed25519(sig: &[u8; 64]) -> Multisig {
274        Multisig(_Multisig::Ed25519(Signature::from_slice(sig).unwrap()))
275    }
276
277    /// Serialize a signature into a writer, in the appropriate
278    /// form for a [legacy message](https://spec.scuttlebutt.nz/feed/messages.html#legacy-json-encoding).
279    pub fn to_legacy<W: Write>(&self, w: &mut W) -> Result<(), io::Error> {
280        match self.0 {
281            _Multisig::Ed25519(ref sig) => {
282                let data = sig.as_base64();
283                w.write_all(data.as_bytes())?;
284                w.write_all(b".sig.ed25519")
285            }
286        }
287    }
288
289    /// Serialize a signature into an owned byte vector,
290    /// in the appropriate form for a
291    /// [legacy message](https://spec.scuttlebutt.nz/feed/messages.html#legacy-json-encoding).
292    pub fn to_legacy_vec(&self) -> Vec<u8> {
293        let mut data = vec![];
294        self.to_legacy(&mut data).unwrap();
295        data
296    }
297
298    /// Serialize a signature into an owned string,
299    /// in the appropriate form for a
300    /// [legacy message](https://spec.scuttlebutt.nz/feed/messages.html#legacy-json-encoding).
301    pub fn to_legacy_string(&self) -> String {
302        String::from_utf8(self.to_legacy_vec()).unwrap()
303    }
304}
305
306/// Everything that can go wrong when decoding a signature from the legacy encoding.
307#[derive(Debug, PartialEq, Eq, Clone)]
308pub enum DecodeSignatureError {
309    /// Input did not contain a `"."` to separate the data from the suffix.
310    NoDot,
311    /// Input did contain the mandatory ".sig".
312    NoDotSig,
313    /// The base64 portion of the key was invalid.
314    InvalidBase64(base64::DecodeError),
315    /// The suffix is not known to this ssb implementation.
316    UnknownSuffix,
317    /// The suffix declares an ed25519 signature, but the data length does not match.
318    Ed25519WrongSize,
319}
320
321impl fmt::Display for DecodeSignatureError {
322    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323        match self {
324            DecodeSignatureError::InvalidBase64(ref err) => write!(f, "{}", err),
325            DecodeSignatureError::NoDot => write!(f, "No dot"),
326            DecodeSignatureError::NoDotSig => write!(f, "No .sig"),
327            DecodeSignatureError::UnknownSuffix => write!(f, "Unknown suffix"),
328            DecodeSignatureError::Ed25519WrongSize => write!(f, "Data of wrong length"),
329        }
330    }
331}
332
333impl std::error::Error for DecodeSignatureError {}
334
335/// The legacy suffix indicating the ed25519 cryptographic primitive.
336const ED25519_SUFFIX: &[u8] = b"ed25519";
337/// Length of a base64 encoded ed25519 public key.
338const ED25519_PK_BASE64_LEN: usize = 44;
339/// Length of a base64 encoded ed25519 public key.
340const ED25519_SIG_BASE64_LEN: usize = 88;
341/// Length of a legacy-encoded ssb ed25519 secret key.
342const SSB_ED25519_SECRET_ENCODED_LEN: usize = 96;
343
344#[test]
345fn test_from_legacy() {
346    let valid_key = b"@zurF8X68ArfRM71dF3mKh36W0xDM8QmOnAS5bYOq8hA=.ed25519";
347    let (key, _) = Multikey::from_legacy(valid_key).unwrap();
348    let legacy_key = key.to_legacy_vec();
349
350    assert_eq!(legacy_key, valid_key);
351
352    assert!(
353        Multikey::from_legacy(b"@zurF8X68ArfRM71dF3mKh36W0xDM8QmOnAS5bYOq8hB=.ed25519").is_err()
354    );
355    assert!(
356        Multikey::from_legacy(b"&zurF8X68ArfRM71dF3mKh36W0xDM8QmOnAS5bYOq8hA=.ed25519").is_err()
357    );
358    assert!(
359        Multikey::from_legacy(b"@zurF8X68ArfRM71dF3mKh36W0xDM8QmOnAS5bYOq8hA=.dd25519").is_err()
360    );
361    assert!(
362        Multikey::from_legacy(b"@zurF8X68ArfRM71dF3mKh36W0xDM8QmOnAS5bYOq8hA=ed25519").is_err()
363    );
364    assert!(
365        Multikey::from_legacy(b"@zurF8X68ArfRM71dF3mKh36W0xDM8QmOnAS5bYOq8hA.ed25519").is_err()
366    );
367    assert!(
368        Multikey::from_legacy(b"@zurF8X68ArfRM71dF3mKh36W0xDM8QmOnAS5bYOq8hA==.ed25519").is_err()
369    );
370}