arcium-core-utils 0.8.1

Arcium core utils
Documentation
//! Pre-#748 ("Rework serialization") serde encodings for the leaf types embedded in the historical
//! v1/v2 circuit formats.
//!
//! Apply with `#[serde(with = "…legacy_codec::legacy")]` on any field whose type is (or contains)
//! one of these leaves. Everything else in v1/v2 -- op enums, `Slice`, `BoxedUint`,
//! `AlgebraicType`, `Batched`, indices -- was unaffected by #748 and needs no wrapper.

use primitives::{
    algebra::{
        elliptic_curve::{Curve, Point},
        field::{FieldExtension, SubfieldElement},
    },
    utils::InPlaceCodec,
};
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};

/// A leaf type whose serde encoding changed in #748, able to (de)serialize as it did before.
pub trait LegacyBytes: Sized {
    fn legacy_serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
    fn legacy_deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>;
}

/// Field elements were `serde_bytes`-encoded canonical little-endian bytes.
impl<F: FieldExtension> LegacyBytes for SubfieldElement<F> {
    fn legacy_serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serde_bytes::serialize(AsRef::<[u8]>::as_ref(&self.to_le_bytes()), serializer)
    }

    fn legacy_deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let bytes: &[u8] = serde_bytes::deserialize(deserializer)?;
        Self::from_le_bytes(bytes).map_err(D::Error::custom)
    }
}

/// Points were `serde_bytes`-encoded `GroupEncoding::to_bytes()` output. `InPlaceCodec` is the only
/// public route to those bytes today, and it additionally reverses them for big-endian curves, so
/// undo that to recover the original ordering.
impl<C: Curve> LegacyBytes for Point<C> {
    fn legacy_serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut bytes = self.to_inplace_bytes();
        if C::POINT_BIG_ENDIAN {
            bytes.reverse();
        }
        serde_bytes::serialize(bytes.as_slice(), serializer)
    }

    fn legacy_deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let bytes: &[u8] = serde_bytes::deserialize(deserializer)?;
        let mut bytes = bytes.to_vec();
        if C::POINT_BIG_ENDIAN {
            bytes.reverse();
        }
        Self::read_le_bytes(&bytes).map_err(D::Error::custom)
    }
}

/// Batches were plain `Vec`s: a length, then each element in its own legacy encoding.
impl<T: LegacyBytes> LegacyBytes for Vec<T> {
    fn legacy_serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.collect_seq(self.iter().map(Legacy))
    }

    fn legacy_deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        Ok(Vec::<Legacy<T>>::deserialize(deserializer)?
            .into_iter()
            .map(|w| w.0)
            .collect())
    }
}

/// Adapter giving a [`LegacyBytes`] value plain `Serialize`/`Deserialize` impls, so derived
/// container encodings (e.g. `Vec`'s) can be reused as-is.
struct Legacy<T>(T);

impl<T: LegacyBytes> Serialize for Legacy<&T> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.0.legacy_serialize(serializer)
    }
}

impl<'de, T: LegacyBytes> Deserialize<'de> for Legacy<T> {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        T::legacy_deserialize(deserializer).map(Legacy)
    }
}

/// `#[serde(with = "…")]` entry point.
pub mod legacy {
    use super::*;

    pub fn serialize<T: LegacyBytes, S: Serializer>(
        value: &T,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        value.legacy_serialize(serializer)
    }

    pub fn deserialize<'de, T: LegacyBytes, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<T, D::Error> {
        T::legacy_deserialize(deserializer)
    }
}