Conversation
--------- Co-authored-by: Kyle Kotowick <kotowick@invictonlabs.com>
Cargo.toml
Outdated
| cfg-if = "1.0.0" | ||
| heapless-bytes = "0.3.0" | ||
| pqcrypto-mldsa = { version = "0.1.0", optional = true} | ||
| serde-big-array = "0.5.1" |
There was a problem hiding this comment.
serde-big-array is unused, yes.
pqcrypto-mldsa is used with dynamic crate selection in the macro, here (and other places).
|
|
||
| #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | ||
| #[serde(try_from = "RawPublicKey")] | ||
| #[serde(try_from = "RawEcPublicKey")] |
There was a problem hiding this comment.
Doesn’t this mean that the MLDSA variants cannot be deserialized?
There was a problem hiding this comment.
We renamed RawPublicKey to RawEcPublicKey (RawElipticCurvePublicKey) to differentiate it from the new RawMldsaPublicKey.
Deserialization for MLDSA is done in the macro, here.
There was a problem hiding this comment.
There are two ways to serialize and deserialize data – using the specific structs like Mldsa44PublicKey, or using the PublicKey enum. With this implementation, you could serialize a PublicKey::Mldsa44(_) or a Mldsa44PublicKey, but you could only deserialize the result into a Mldsa44PublicKey, not into a PublicKey.
| #[cfg(feature = "backend-dilithium2")] | ||
| use cosey::Dilithium2PublicKey; | ||
| #[cfg(feature = "backend-dilithium3")] | ||
| use cosey::Dilithium3PublicKey; | ||
| #[cfg(feature = "backend-dilithium5")] | ||
| use cosey::Dilithium5PublicKey; | ||
| #[cfg(feature = "backend-dilithium2")] | ||
| use pqcrypto_dilithium::dilithium2; | ||
| #[cfg(feature = "backend-dilithium3")] | ||
| use pqcrypto_dilithium::dilithium3; | ||
| #[cfg(feature = "backend-dilithium5")] | ||
| use pqcrypto_dilithium::dilithium5; |
There was a problem hiding this comment.
These features are missing from Cargo.toml
| heapless-bytes = "0.3.0" | ||
| pqcrypto-mldsa = { version = "0.1.0", optional = true} | ||
| serde_repr = "0.1" | ||
| paste = "1.0" |
There was a problem hiding this comment.
Paste is unmaintained. Please remove it.
| #[cfg(feature = "mldsa")] | ||
| macro_rules! mldsa_public_key { | ||
| ($mldsa_number: tt) => { | ||
| paste! { | ||
| with_eager_expansions! { | ||
| #[derive(Clone, Debug, Eq, PartialEq, Serialize)] | ||
| #[serde(into = #{ concat!("RawMldsa", stringify!($mldsa_number), "PublicKey") })] | ||
| pub struct [<Mldsa $mldsa_number PublicKey>] { | ||
| pub pk: Bytes<{ [<mldsa $mldsa_number>]::public_key_bytes() }>, | ||
| } | ||
|
|
||
| impl PublicKeyConstants for [<Mldsa $mldsa_number PublicKey>] { | ||
| const KTY: Kty = Kty::Pqc; | ||
| const ALG: Alg = Alg::[<Mldsa $mldsa_number>]; | ||
| const CRV: Crv = Crv::None; | ||
| } | ||
|
|
||
| impl From<[<Mldsa $mldsa_number PublicKey>]> for PublicKey { | ||
| fn from(key: [<Mldsa $mldsa_number PublicKey>]) -> Self { | ||
| PublicKey::[<Mldsa $mldsa_number>](key) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Default)] | ||
| struct [<RawMldsa $mldsa_number PublicKey>] { | ||
| kty: Option<Kty>, | ||
| alg: Option<Alg>, | ||
| pk: Option<Bytes<{ [<mldsa $mldsa_number>]::public_key_bytes() }>>, | ||
| } | ||
|
|
||
| impl From<[<Mldsa $mldsa_number PublicKey>]> for [<RawMldsa $mldsa_number PublicKey>] { | ||
| fn from(key: [<Mldsa $mldsa_number PublicKey>]) -> Self { | ||
| Self { | ||
| kty: Some([<Mldsa $mldsa_number PublicKey>]::KTY), | ||
| alg: Some([<Mldsa $mldsa_number PublicKey>]::ALG), | ||
| pk: Some(key.pk), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<'de> serde::Deserialize<'de> for [<Mldsa $mldsa_number PublicKey>] { | ||
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
| where | ||
| D: serde::Deserializer<'de>, | ||
| { | ||
| let [<RawMldsa $mldsa_number PublicKey>] { kty, alg, pk, .. } = | ||
| [<RawMldsa $mldsa_number PublicKey>]::deserialize(deserializer)?; | ||
| check_key_constants::<[<Mldsa $mldsa_number PublicKey>], D::Error>(kty, alg, Some(Crv::None))?; | ||
| let pk = pk.ok_or_else(|| D::Error::missing_field("pk"))?; | ||
| Ok(Self { pk }) | ||
| } | ||
| } | ||
|
|
||
| impl<'de> Deserialize<'de> for [<RawMldsa $mldsa_number PublicKey>] { | ||
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
| where | ||
| D: serde::Deserializer<'de>, | ||
| { | ||
| struct IndexedVisitor; | ||
| impl<'de> serde::de::Visitor<'de> for IndexedVisitor { | ||
| type Value = [<RawMldsa $mldsa_number PublicKey>]; | ||
|
|
||
| fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { | ||
| formatter.write_str(concat!("RawMldsa", stringify!($mldsa_number), "PublicKey")) | ||
| } | ||
|
|
||
| fn visit_map<V>(self, mut map: V) -> Result<[<RawMldsa $mldsa_number PublicKey>], V::Error> | ||
| where | ||
| V: MapAccess<'de>, | ||
| { | ||
| #[derive(PartialEq)] | ||
| enum Key { | ||
| Label(Label), | ||
| Unknown(i8), | ||
| None, | ||
| } | ||
|
|
||
| fn next_key<'a, V: MapAccess<'a>>(map: &mut V) -> Result<Key, V::Error> { | ||
| let key: Option<i8> = map.next_key()?; | ||
| let key = match key { | ||
| Some(key) => match Label::try_from(key) { | ||
| Ok(label) => Key::Label(label), | ||
| Err(_) => Key::Unknown(key), | ||
| }, | ||
| None => Key::None, | ||
| }; | ||
| Ok(key) | ||
| } | ||
|
|
||
| let mut public_key = [<RawMldsa $mldsa_number PublicKey>]::default(); | ||
|
|
||
| // As we cannot deserialize arbitrary values with cbor-smol, we do not support | ||
| // unknown keys before a known key. If there are unknown keys, they must be at the | ||
| // end. | ||
|
|
||
| // only deserialize in canonical order | ||
|
|
||
| let mut key = next_key(&mut map)?; | ||
|
|
||
| if key == Key::Label(Label::Kty) { | ||
| public_key.kty = Some(map.next_value()?); | ||
| key = next_key(&mut map)?; | ||
| } | ||
|
|
||
| if key == Key::Label(Label::Alg) { | ||
| public_key.alg = Some(map.next_value()?); | ||
| key = next_key(&mut map)?; | ||
| } | ||
|
|
||
| if key == Key::Label(Label::CrvOrPk) { | ||
| public_key.pk = Some(map.next_value()?); | ||
| key = next_key(&mut map)?; | ||
| } | ||
|
|
||
| // if there is another key, it should be an unknown one | ||
| if matches!(key, Key::Label(_)) { | ||
| Err(serde::de::Error::custom( | ||
| "public key data in wrong order or with duplicates", | ||
| )) | ||
| } else { | ||
| Ok(public_key) | ||
| } | ||
| } | ||
| } | ||
| deserializer.deserialize_map(IndexedVisitor {}) | ||
| } | ||
| } | ||
|
|
||
| impl Serialize for [<RawMldsa $mldsa_number PublicKey>] { | ||
| fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> | ||
| where | ||
| S: serde::Serializer, | ||
| { | ||
| let is_set = [self.kty.is_some(), self.alg.is_some(), self.pk.is_some()]; | ||
| let fields = is_set.into_iter().map(usize::from).sum(); | ||
| use serde::ser::SerializeMap; | ||
| let mut map = serializer.serialize_map(Some(fields))?; | ||
|
|
||
| // 1: kty | ||
| if let Some(kty) = &self.kty { | ||
| map.serialize_entry(&(Label::Kty as i8), &(*kty as i8))?; | ||
| } | ||
| // 3: alg | ||
| if let Some(alg) = &self.alg { | ||
| map.serialize_entry(&(Label::Alg as i8), &(*alg as i8))?; | ||
| } | ||
| // -1: pk | ||
| if let Some(pk) = &self.pk { | ||
| map.serialize_entry(&(Label::CrvOrPk as i8), pk)?; | ||
| } | ||
|
|
||
| map.end() | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| #[cfg(feature = "mldsa44")] | ||
| mldsa_public_key!(44); | ||
| #[cfg(feature = "mldsa65")] | ||
| mldsa_public_key!(65); | ||
| #[cfg(feature = "mldsa87")] | ||
| mldsa_public_key!(87); |
There was a problem hiding this comment.
Is Paste really necessary?
You should be able to work around the need for paste by making the macro_rules require the identifier:
mldsa_public_key!(44, MlDsa44PublicKey, RawMlDsa44PublicKey);| #[test] | ||
| #[cfg(feature = "backend-dilithium2")] | ||
| fn de_dilithium2() { | ||
| const DILITHIUM2_KAT_PK: &str = "1c0ee1111b08003f28e65e8b3bdeb037cf8f221dfcdaf5950edb38d506d85bef6177e3de0d4f1ef5847735947b56d08e841db2444fa2b729adeb1417ca7adf42a1490c5a097f002760c1fc419be8325aad0197c52ced80d3df18e7774265b289912ceca1be3a90d8a4fde65c84c610864e47deecae3eea4430b9909559408d11a6abdb7db9336df7f96eab4864a6579791265fa56c348cb7d2ddc90e133a95c3f6b13601429f5408bd999aa479c1018159550ec55a113c493be648f4e036dd4f8c809e036b4fbb918c2c484ad8e1747ae05585ab433fdf461af03c25a773700721aa05f7379fe7f5ed96175d4021076e7f52b60308eff5d42ba6e093b3d0815eb3496646e49230a9b35c8d41900c2bb8d3b446a23127f7e096d85a1c794ad4c89277904fc6bfec57b1cdd80df9955030fdca741afbdac827b13ccd5403588af4644003c2265dfa4d419dbccd2064892386518be9d51c16498275ebecf5cdc7a820f2c29314ac4a6f08b2252ad3cfb199aa42fe0b4fb571975c1020d949e194ee1ead937bfb550bb3ba8e357a029c29f077554602e1ca2f2289cb9169941c3aafdb8e58c7f2ac77291fb4147c65f6b031d3eba42f2acfd9448a5bc22b476e07ccceda2306c554ec9b7ab655f1d7318c2b7e67d5f69bedf56000fda98986b5ab1b3a22d8dfd6681697b23a55c96e8710f3f98c044fb15f606313ee56c0f1f5ca0f512e08484fcb358e6e528ffa89f8a866ccff3c0c5813147ec59af0470c4aad0141d34f101da2e5e1bd52d0d4c9b13b3e3d87d1586105796754e7978ca1c68a7d85df112b7ab921b359a9f03cbd27a7eac87a9a80b0b26b4c9657ed85ad7fa2616ab345eb8226f69fc0f48183ff574bcd767b5676413adb12ea2150a0e97683ee54243c25b7ea8a718606f86993d8d0dace834ed341eeb724fe3d5ff0bc8b8a7b8104ba269d34133a4cf8300a2d688496b59b6fcbc61ae96062ea1d8e5b410c5671f424417ed693329cd983001ffcd10023d598859fb7ad5fd263547117100690c6ce7438956e6cc57f1b5de53bb0dc72ce9b6deaa85789599a70f0051f1a0e25e86d888b00df36bdbc93ef7217c45ace11c0790d70e9953e5b417ba2fd9a4caf82f1fce6f45f53e215b8355ef61d891df1c794231c162dd24164b534a9d48467cdc323624c2f95d4402ff9d66ab1191a8124144afa35d4e31dc86caa797c31f68b85854cd959c4fac5ec53b3b56d374b888a9e979a6576b6345ec8522c9606990281bf3ef7c5945d10fd21a2a1d2e5404c5cf21220641391b98bcf825398305b56e58b611fe5253203e3df0d22466a73b3f0fbe43b9a62928091898b8a0e5b269db586b0e4ddef50d682a12d2c1be824149aa254c6381bb412d77c3f9aa902b688c81715a59c839558556d35ed4fc83b4ab18181f40f73dcd76860d8d8bf94520237c2ac0e463ba09e3c9782380dc07fe4fcba340cc2003439fd2314610638070d6c9eea0a70bae83b5d5d3c5d3fde26dd01606c8c520158e7e5104020f248ceaa666457c10aebf068f8a3bd5ce7b52c6af0abd5944af1ad4752c9113976083c03b6c34e1d47ed69644cad782c2f7d05f8a148961d965fa2e1723a8ddebc22a90cd783dd1f4db38fb9ae5a6714b3d946781643d317b7dd79381cf789a9588bb3e193b92a0b60d6b07d047f6984b0609ec57543c394ca8d5e5bcc2a731a79618bd1e2e0da8704af98f20f5f8f5452ddf646b95b341dd7f0d2cc1fa15bd9895cd5b65aa1cb94b5e2e788fda9825b656639193d98328154a4f2c35495a38b6ea0d2ffaaa35df92c203c7f31cbbca7bd03c3c2302190cecd161fd49237e4f839e3f3"; |
There was a problem hiding this comment.
Please document the source of these keys. Are they test vectors ? Please document their source.
This PR adds support for ML-DSA assertions.
Recreate of #11 after changing the source branch name.