Skip to main content

affinidi_encoding/
multicodec.rs

1//! Multicodec encoding/decoding
2//!
3//! Multicodec is a self-describing format that prefixes data with a varint
4//! indicating the type of data that follows.
5//!
6//! See: <https://github.com/multiformats/multicodec>
7
8use crate::EncodingError;
9use serde::{Deserialize, Serialize};
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12// ****************************************************************************
13// Codec Magic Numbers
14// See: https://github.com/multiformats/multicodec/blob/master/table.csv
15// ****************************************************************************
16pub const ED25519_PUB: u64 = 0xed;
17pub const ED25519_PRIV: u64 = 0x1300;
18pub const X25519_PUB: u64 = 0xec;
19pub const X25519_PRIV: u64 = 0x1302;
20pub const SECP256K1_PUB: u64 = 0xe7;
21pub const SECP256K1_PRIV: u64 = 0x1301;
22pub const P256_PUB: u64 = 0x1200;
23pub const P256_PRIV: u64 = 0x1306;
24pub const P384_PUB: u64 = 0x1201;
25pub const P384_PRIV: u64 = 0x1307;
26pub const P521_PUB: u64 = 0x1202;
27pub const P521_PRIV: u64 = 0x1308;
28
29/// Known codec types
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31pub enum Codec {
32    Ed25519Pub,
33    Ed25519Priv,
34    X25519Pub,
35    X25519Priv,
36    Secp256k1Pub,
37    Secp256k1Priv,
38    P256Pub,
39    P256Priv,
40    P384Pub,
41    P384Priv,
42    P521Pub,
43    P521Priv,
44    Unknown(u64),
45}
46
47impl Codec {
48    /// Convert a raw codec value to a Codec enum
49    pub fn from_u64(value: u64) -> Self {
50        match value {
51            ED25519_PUB => Codec::Ed25519Pub,
52            ED25519_PRIV => Codec::Ed25519Priv,
53            X25519_PUB => Codec::X25519Pub,
54            X25519_PRIV => Codec::X25519Priv,
55            SECP256K1_PUB => Codec::Secp256k1Pub,
56            SECP256K1_PRIV => Codec::Secp256k1Priv,
57            P256_PUB => Codec::P256Pub,
58            P256_PRIV => Codec::P256Priv,
59            P384_PUB => Codec::P384Pub,
60            P384_PRIV => Codec::P384Priv,
61            P521_PUB => Codec::P521Pub,
62            P521_PRIV => Codec::P521Priv,
63            other => Codec::Unknown(other),
64        }
65    }
66
67    /// Convert to raw u64 value
68    pub fn to_u64(self) -> u64 {
69        match self {
70            Codec::Ed25519Pub => ED25519_PUB,
71            Codec::Ed25519Priv => ED25519_PRIV,
72            Codec::X25519Pub => X25519_PUB,
73            Codec::X25519Priv => X25519_PRIV,
74            Codec::Secp256k1Pub => SECP256K1_PUB,
75            Codec::Secp256k1Priv => SECP256K1_PRIV,
76            Codec::P256Pub => P256_PUB,
77            Codec::P256Priv => P256_PRIV,
78            Codec::P384Pub => P384_PUB,
79            Codec::P384Priv => P384_PRIV,
80            Codec::P521Pub => P521_PUB,
81            Codec::P521Priv => P521_PRIV,
82            Codec::Unknown(v) => v,
83        }
84    }
85
86    /// Returns true if this is a public key codec
87    pub fn is_public(&self) -> bool {
88        matches!(
89            self,
90            Codec::Ed25519Pub
91                | Codec::X25519Pub
92                | Codec::Secp256k1Pub
93                | Codec::P256Pub
94                | Codec::P384Pub
95                | Codec::P521Pub
96        )
97    }
98
99    /// Returns the expected key length for this codec, if known
100    pub fn expected_key_length(&self) -> Option<usize> {
101        match self {
102            Codec::Ed25519Pub | Codec::Ed25519Priv => Some(32),
103            Codec::X25519Pub | Codec::X25519Priv => Some(32),
104            Codec::Secp256k1Pub => Some(33), // compressed
105            Codec::P256Pub => Some(33),      // compressed
106            Codec::P384Pub => Some(49),      // compressed
107            Codec::P521Pub => Some(67),      // compressed
108            _ => None,
109        }
110    }
111}
112
113/// A multicodec-encoded byte slice (borrowed)
114#[derive(Zeroize, ZeroizeOnDrop)]
115pub struct MultiEncoded([u8]);
116
117impl MultiEncoded {
118    /// Create a new multiencoded byte slice
119    /// Validates the codec encoding
120    pub fn new(bytes: &[u8]) -> Result<&Self, EncodingError> {
121        unsigned_varint::decode::u64(bytes)
122            .map_err(|e| EncodingError::InvalidMulticodec(format!("varint decode: {e}")))?;
123
124        Ok(unsafe { &*(bytes as *const [u8] as *const MultiEncoded) })
125    }
126
127    /// Size of the byte array (including codec prefix)
128    pub fn len(&self) -> usize {
129        self.0.len()
130    }
131
132    /// Returns true if empty
133    pub fn is_empty(&self) -> bool {
134        self.0.is_empty()
135    }
136
137    /// Separates the codec and the data
138    pub fn parts(&self) -> (u64, &[u8]) {
139        unsigned_varint::decode::u64(&self.0).unwrap()
140    }
141
142    /// Raw codec value (u64)
143    pub fn codec(&self) -> u64 {
144        self.parts().0
145    }
146
147    /// Codec as typed enum
148    pub fn codec_type(&self) -> Codec {
149        Codec::from_u64(self.codec())
150    }
151
152    /// Data bytes (without codec prefix)
153    pub fn data(&self) -> &[u8] {
154        self.parts().1
155    }
156
157    /// Returns the raw bytes, including the codec prefix
158    pub fn as_bytes(&self) -> &[u8] {
159        &self.0
160    }
161}
162
163/// A multicodec-encoded byte buffer (owned)
164#[derive(Clone, Zeroize, ZeroizeOnDrop)]
165pub struct MultiEncodedBuf(Vec<u8>);
166
167impl MultiEncodedBuf {
168    /// Parse an existing multicodec-encoded buffer
169    pub fn new(bytes: Vec<u8>) -> Result<Self, EncodingError> {
170        unsigned_varint::decode::u64(&bytes)
171            .map_err(|e| EncodingError::InvalidMulticodec(format!("varint decode: {e}")))?;
172        Ok(Self(bytes))
173    }
174
175    /// Encode bytes with the given codec
176    pub fn encode(codec: Codec, bytes: &[u8]) -> Self {
177        Self::encode_raw(codec.to_u64(), bytes)
178    }
179
180    /// Encode bytes with a raw codec value (backwards-compatible alias)
181    pub fn encode_bytes(codec: u64, bytes: &[u8]) -> Self {
182        Self::encode_raw(codec, bytes)
183    }
184
185    /// Encode bytes with a raw codec value
186    pub fn encode_raw(codec: u64, bytes: &[u8]) -> Self {
187        let mut codec_buffer = [0u8; 10];
188        let encoded_codec = unsigned_varint::encode::u64(codec, &mut codec_buffer);
189        let mut result = Vec::with_capacity(encoded_codec.len() + bytes.len());
190        result.extend(encoded_codec);
191        result.extend(bytes);
192        Self(result)
193    }
194
195    /// Returns the raw bytes, including the codec prefix
196    /// Note: clones due to ZeroizeOnDrop
197    pub fn into_bytes(self) -> Vec<u8> {
198        self.0.clone()
199    }
200
201    /// Returns a reference to the raw bytes
202    pub fn as_bytes(&self) -> &[u8] {
203        &self.0
204    }
205
206    /// Borrow as MultiEncoded slice
207    pub fn as_multi_encoded(&self) -> &MultiEncoded {
208        unsafe { &*(self.0.as_slice() as *const [u8] as *const MultiEncoded) }
209    }
210}
211
212impl AsRef<MultiEncoded> for MultiEncodedBuf {
213    fn as_ref(&self) -> &MultiEncoded {
214        self.as_multi_encoded()
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_encode_decode_ed25519() {
224        let key_bytes = [0u8; 32];
225        let encoded = MultiEncodedBuf::encode(Codec::Ed25519Pub, &key_bytes);
226
227        let decoded = MultiEncoded::new(encoded.as_bytes()).unwrap();
228        assert_eq!(decoded.codec(), ED25519_PUB);
229        assert_eq!(decoded.codec_type(), Codec::Ed25519Pub);
230        assert_eq!(decoded.data(), &key_bytes);
231    }
232
233    #[test]
234    fn test_codec_roundtrip() {
235        for codec in [
236            Codec::Ed25519Pub,
237            Codec::X25519Pub,
238            Codec::P256Pub,
239            Codec::P384Pub,
240        ] {
241            let raw = codec.to_u64();
242            assert_eq!(Codec::from_u64(raw), codec);
243        }
244    }
245}