Skip to content

Conversation

KyleKotowick
Copy link

This PR adds support for ML-DSA assertions.

Recreate of #11 after changing the source branch name.

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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused dependency?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn’t this mean that the MLDSA variants cannot be deserialized?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We renamed RawPublicKey to RawEcPublicKey (RawElipticCurvePublicKey) to differentiate it from the new RawMldsaPublicKey.

Deserialization for MLDSA is done in the macro, here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +11 to +22
#[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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paste is unmaintained. Please remove it.

Comment on lines +567 to +731
#[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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document the source of these keys. Are they test vectors ? Please document their source.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants