1use crate::EncodingError;
9use serde::{Deserialize, Serialize};
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12pub 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;
28pub const BLS12381_G1_PUB: u64 = 0xea;
31pub const BLS12381_G2_PUB: u64 = 0xeb;
32
33pub const ML_DSA_44_PUB: u64 = 0x1210;
47pub const ML_DSA_44_PRIV_SEED: u64 = 0x131a;
48pub const ML_DSA_65_PUB: u64 = 0x1211;
49pub const ML_DSA_65_PRIV_SEED: u64 = 0x131b;
50pub const ML_DSA_87_PUB: u64 = 0x1212;
51pub const ML_DSA_87_PRIV_SEED: u64 = 0x131c;
52pub const SLH_DSA_SHA2_128S_PUB: u64 = 0x1220;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub enum Codec {
57 Ed25519Pub,
58 Ed25519Priv,
59 X25519Pub,
60 X25519Priv,
61 Secp256k1Pub,
62 Secp256k1Priv,
63 P256Pub,
64 P256Priv,
65 P384Pub,
66 P384Priv,
67 P521Pub,
68 P521Priv,
69 Bls12381G1Pub,
70 Bls12381G2Pub,
71 MlDsa44Pub,
72 MlDsa44PrivSeed,
73 MlDsa65Pub,
74 MlDsa65PrivSeed,
75 MlDsa87Pub,
76 MlDsa87PrivSeed,
77 SlhDsaSha2_128sPub,
78 Unknown(u64),
79}
80
81impl Codec {
82 pub fn from_u64(value: u64) -> Self {
84 match value {
85 ED25519_PUB => Codec::Ed25519Pub,
86 ED25519_PRIV => Codec::Ed25519Priv,
87 X25519_PUB => Codec::X25519Pub,
88 X25519_PRIV => Codec::X25519Priv,
89 SECP256K1_PUB => Codec::Secp256k1Pub,
90 SECP256K1_PRIV => Codec::Secp256k1Priv,
91 P256_PUB => Codec::P256Pub,
92 P256_PRIV => Codec::P256Priv,
93 P384_PUB => Codec::P384Pub,
94 P384_PRIV => Codec::P384Priv,
95 P521_PUB => Codec::P521Pub,
96 P521_PRIV => Codec::P521Priv,
97 BLS12381_G1_PUB => Codec::Bls12381G1Pub,
98 BLS12381_G2_PUB => Codec::Bls12381G2Pub,
99 ML_DSA_44_PUB => Codec::MlDsa44Pub,
100 ML_DSA_44_PRIV_SEED => Codec::MlDsa44PrivSeed,
101 ML_DSA_65_PUB => Codec::MlDsa65Pub,
102 ML_DSA_65_PRIV_SEED => Codec::MlDsa65PrivSeed,
103 ML_DSA_87_PUB => Codec::MlDsa87Pub,
104 ML_DSA_87_PRIV_SEED => Codec::MlDsa87PrivSeed,
105 SLH_DSA_SHA2_128S_PUB => Codec::SlhDsaSha2_128sPub,
106 other => Codec::Unknown(other),
107 }
108 }
109
110 pub fn to_u64(self) -> u64 {
112 match self {
113 Codec::Ed25519Pub => ED25519_PUB,
114 Codec::Ed25519Priv => ED25519_PRIV,
115 Codec::X25519Pub => X25519_PUB,
116 Codec::X25519Priv => X25519_PRIV,
117 Codec::Secp256k1Pub => SECP256K1_PUB,
118 Codec::Secp256k1Priv => SECP256K1_PRIV,
119 Codec::P256Pub => P256_PUB,
120 Codec::P256Priv => P256_PRIV,
121 Codec::P384Pub => P384_PUB,
122 Codec::P384Priv => P384_PRIV,
123 Codec::P521Pub => P521_PUB,
124 Codec::P521Priv => P521_PRIV,
125 Codec::Bls12381G1Pub => BLS12381_G1_PUB,
126 Codec::Bls12381G2Pub => BLS12381_G2_PUB,
127 Codec::MlDsa44Pub => ML_DSA_44_PUB,
128 Codec::MlDsa44PrivSeed => ML_DSA_44_PRIV_SEED,
129 Codec::MlDsa65Pub => ML_DSA_65_PUB,
130 Codec::MlDsa65PrivSeed => ML_DSA_65_PRIV_SEED,
131 Codec::MlDsa87Pub => ML_DSA_87_PUB,
132 Codec::MlDsa87PrivSeed => ML_DSA_87_PRIV_SEED,
133 Codec::SlhDsaSha2_128sPub => SLH_DSA_SHA2_128S_PUB,
134 Codec::Unknown(v) => v,
135 }
136 }
137
138 pub fn is_public(&self) -> bool {
140 matches!(
141 self,
142 Codec::Ed25519Pub
143 | Codec::X25519Pub
144 | Codec::Secp256k1Pub
145 | Codec::P256Pub
146 | Codec::P384Pub
147 | Codec::P521Pub
148 | Codec::Bls12381G1Pub
149 | Codec::Bls12381G2Pub
150 | Codec::MlDsa44Pub
151 | Codec::MlDsa65Pub
152 | Codec::MlDsa87Pub
153 | Codec::SlhDsaSha2_128sPub )
155 }
156
157 pub fn expected_key_length(&self) -> Option<usize> {
159 match self {
160 Codec::Ed25519Pub | Codec::Ed25519Priv => Some(32),
161 Codec::X25519Pub | Codec::X25519Priv => Some(32),
162 Codec::Secp256k1Pub => Some(33), Codec::P256Pub => Some(33), Codec::P384Pub => Some(49), Codec::P521Pub => Some(67), Codec::Bls12381G1Pub => Some(48),
168 Codec::Bls12381G2Pub => Some(96),
169 Codec::MlDsa44Pub => Some(1312),
171 Codec::MlDsa65Pub => Some(1952),
172 Codec::MlDsa87Pub => Some(2592),
173 Codec::MlDsa44PrivSeed | Codec::MlDsa65PrivSeed | Codec::MlDsa87PrivSeed => Some(32),
175 Codec::SlhDsaSha2_128sPub => Some(32),
177 _ => None,
178 }
179 }
180}
181
182#[derive(Zeroize, ZeroizeOnDrop)]
188#[repr(transparent)]
189pub struct MultiEncoded([u8]);
190
191impl MultiEncoded {
192 pub fn new(bytes: &[u8]) -> Result<&Self, EncodingError> {
195 unsigned_varint::decode::u64(bytes)
196 .map_err(|e| EncodingError::InvalidMulticodec(format!("varint decode: {e}")))?;
197
198 Ok(unsafe { &*(bytes as *const [u8] as *const MultiEncoded) })
204 }
205
206 pub fn len(&self) -> usize {
208 self.0.len()
209 }
210
211 pub fn is_empty(&self) -> bool {
213 self.0.is_empty()
214 }
215
216 pub fn parts(&self) -> (u64, &[u8]) {
218 unsigned_varint::decode::u64(&self.0).unwrap()
219 }
220
221 pub fn codec(&self) -> u64 {
223 self.parts().0
224 }
225
226 pub fn codec_type(&self) -> Codec {
228 Codec::from_u64(self.codec())
229 }
230
231 pub fn data(&self) -> &[u8] {
233 self.parts().1
234 }
235
236 pub fn as_bytes(&self) -> &[u8] {
238 &self.0
239 }
240}
241
242#[derive(Clone, Zeroize, ZeroizeOnDrop)]
244pub struct MultiEncodedBuf(Vec<u8>);
245
246impl MultiEncodedBuf {
247 pub fn new(bytes: Vec<u8>) -> Result<Self, EncodingError> {
249 unsigned_varint::decode::u64(&bytes)
250 .map_err(|e| EncodingError::InvalidMulticodec(format!("varint decode: {e}")))?;
251 Ok(Self(bytes))
252 }
253
254 pub fn encode(codec: Codec, bytes: &[u8]) -> Self {
256 Self::encode_raw(codec.to_u64(), bytes)
257 }
258
259 pub fn encode_bytes(codec: u64, bytes: &[u8]) -> Self {
261 Self::encode_raw(codec, bytes)
262 }
263
264 pub fn encode_raw(codec: u64, bytes: &[u8]) -> Self {
266 let mut codec_buffer = [0u8; 10];
267 let encoded_codec = unsigned_varint::encode::u64(codec, &mut codec_buffer);
268 let mut result = Vec::with_capacity(encoded_codec.len() + bytes.len());
269 result.extend(encoded_codec);
270 result.extend(bytes);
271 Self(result)
272 }
273
274 pub fn into_bytes(self) -> Vec<u8> {
277 self.0.clone()
278 }
279
280 pub fn as_bytes(&self) -> &[u8] {
282 &self.0
283 }
284
285 pub fn as_multi_encoded(&self) -> &MultiEncoded {
287 unsafe { &*(self.0.as_slice() as *const [u8] as *const MultiEncoded) }
291 }
292}
293
294impl AsRef<MultiEncoded> for MultiEncodedBuf {
295 fn as_ref(&self) -> &MultiEncoded {
296 self.as_multi_encoded()
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_encode_decode_ed25519() {
306 let key_bytes = [0u8; 32];
307 let encoded = MultiEncodedBuf::encode(Codec::Ed25519Pub, &key_bytes);
308
309 let decoded = MultiEncoded::new(encoded.as_bytes()).unwrap();
310 assert_eq!(decoded.codec(), ED25519_PUB);
311 assert_eq!(decoded.codec_type(), Codec::Ed25519Pub);
312 assert_eq!(decoded.data(), &key_bytes);
313 }
314
315 #[test]
316 fn test_codec_roundtrip() {
317 for codec in [
318 Codec::Ed25519Pub,
319 Codec::X25519Pub,
320 Codec::P256Pub,
321 Codec::P384Pub,
322 ] {
323 let raw = codec.to_u64();
324 assert_eq!(Codec::from_u64(raw), codec);
325 }
326 }
327
328 #[test]
346 fn pqc_code_points_match_the_multicodec_registry() {
347 assert_eq!(ML_DSA_44_PUB, 0x1210, "mldsa-44-pub");
349 assert_eq!(ML_DSA_65_PUB, 0x1211, "mldsa-65-pub");
350 assert_eq!(ML_DSA_87_PUB, 0x1212, "mldsa-87-pub");
351
352 assert_eq!(ML_DSA_44_PRIV_SEED, 0x131a, "mldsa-44-priv-seed");
359 assert_eq!(ML_DSA_65_PRIV_SEED, 0x131b, "mldsa-65-priv-seed");
360 assert_eq!(ML_DSA_87_PRIV_SEED, 0x131c, "mldsa-87-priv-seed");
361
362 assert_eq!(SLH_DSA_SHA2_128S_PUB, 0x1220, "slhdsa-sha2-128s-pub");
364
365 assert_eq!(Codec::MlDsa44Pub.expected_key_length(), Some(1312));
368 assert_eq!(Codec::MlDsa65Pub.expected_key_length(), Some(1952));
369 assert_eq!(Codec::MlDsa87Pub.expected_key_length(), Some(2592));
370 assert_eq!(Codec::MlDsa44PrivSeed.expected_key_length(), Some(32));
371 assert_eq!(Codec::MlDsa65PrivSeed.expected_key_length(), Some(32));
372 assert_eq!(Codec::MlDsa87PrivSeed.expected_key_length(), Some(32));
373 assert_eq!(Codec::SlhDsaSha2_128sPub.expected_key_length(), Some(32));
374
375 for codec in [
377 Codec::MlDsa44Pub,
378 Codec::MlDsa65Pub,
379 Codec::MlDsa87Pub,
380 Codec::MlDsa44PrivSeed,
381 Codec::MlDsa65PrivSeed,
382 Codec::MlDsa87PrivSeed,
383 Codec::SlhDsaSha2_128sPub,
384 ] {
385 assert_eq!(Codec::from_u64(codec.to_u64()), codec, "{codec:?}");
386 }
387 }
388
389 #[test]
405 fn slh_dsa_has_no_private_code_point_to_hold() {
406 assert!(Codec::SlhDsaSha2_128sPub.is_public());
408
409 for codec in [
412 Codec::MlDsa44PrivSeed,
413 Codec::MlDsa65PrivSeed,
414 Codec::MlDsa87PrivSeed,
415 ] {
416 assert!(!codec.is_public(), "{codec:?} must classify as private");
417 }
418 }
419}