Repository: dusk-network/bls12_381 (language: rust)

Issue:
Implement `rkyv` related traits for `BlsScalar`
**Describe what you want implemented** 
`rkyv` related traits should be implemented for `BlsScalar` to allow it to pass through the host/VM boundary.

**Describe "Why" this is needed**
This is necessary to be able to pass `BlsScalar`s to a smart contract running on the virtual machine.

**Describe alternatives you've considered**
N/A

**Additional context**
The job of serialization and de-serialization used to be performed by `canonical`. Due to the increased costs it entailed, we decided to architect the new VM to allow `rkyv`able used as transaction arguments.



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a856662..646f05e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 
 ### Added
+- `rkyv` implementation behind feature gate [#90](https://github.com/dusk-network/bls12_381/issues/90)
 - Derive `Hash` for `BlsScalar`
 - Apply patches from `zkcrypto` to improve the efficiency [#86](https://github.com/dusk-network/bls12_381/issues/86)
 
diff --git a/Cargo.toml b/Cargo.toml
index 52628713..7a7a4e7a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ serde = {version = "1.0", optional = true}
 rand_core = {version = "0.6", default-features = false}
 canonical = {version = "0.7", optional = true}
 canonical_derive = {version = "0.7", optional = true}
+rkyv = {version = "0.7", optional = true}
 dusk-bytes = "0.1"
 
 [package.metadata.docs.rs]
diff --git a/src/fp.rs b/src/fp.rs
index 7c9ac9a1..22cbc2b6 100644
--- a/src/fp.rs
+++ b/src/fp.rs
@@ -12,6 +12,9 @@ use serde::{
     self, de::Visitor, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer,
 };
 
+#[cfg(feature = "rkyv")]
+use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
+
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
 use crate::util::{adc, mac, sbb};
@@ -21,6 +24,7 @@ use crate::util::{adc, mac, sbb};
 // Montgomery form; i.e., Scalar(a) = aR mod p, with R = 2^384.
 #[derive(Copy, Clone)]
 #[cfg_attr(feature = "canon", derive(Canon))]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
 pub struct Fp([u64; 6]);
 
 impl fmt::Debug for Fp {
diff --git a/src/fp12.rs b/src/fp12.rs
index ac74f6a4..2261c528 100644
--- a/src/fp12.rs
+++ b/src/fp12.rs
@@ -8,8 +8,12 @@ use core::fmt;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
+#[cfg(feature = "rkyv")]
+use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
+
 /// This represents an element $c_0 + c_1 w$ of $\mathbb{F}_{p^12} = \mathbb{F}_{p^6} / w^2 - v$.
 #[cfg_attr(feature = "canon", derive(Canon))]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
 pub struct Fp12 {
     pub c0: Fp6,
     pub c1: Fp6,
diff --git a/src/fp2.rs b/src/fp2.rs
index ec31f1bc..a1a57bb6 100644
--- a/src/fp2.rs
+++ b/src/fp2.rs
@@ -11,10 +11,14 @@ use serde::{
     self, de::Visitor, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
 };
 
+#[cfg(feature = "rkyv")]
+use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
+
 use crate::fp::Fp;
 
 #[derive(Copy, Clone)]
 #[cfg_attr(feature = "canon", derive(Canon))]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
 pub struct Fp2 {
     pub c0: Fp,
     pub c1: Fp,
diff --git a/src/fp6.rs b/src/fp6.rs
index adc57391..09c389ba 100644
--- a/src/fp6.rs
+++ b/src/fp6.rs
@@ -7,6 +7,9 @@ use core::fmt;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
+#[cfg(feature = "rkyv")]
+use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
+
 #[cfg(feature = "serde_req")]
 use serde::{
     self, de::Visitor, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
@@ -14,6 +17,7 @@ use serde::{
 
 /// This represents an element $c_0 + c_1 v + c_2 v^2$ of $\mathbb{F}_{p^6} = \mathbb{F}_{p^2} / v^3 - u - 1$.
 #[cfg_attr(feature = "canon", derive(Canon))]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
 pub struct Fp6 {
     pub c0: Fp2,
     pub c1: Fp2,
diff --git a/src/g1.rs b/src/g1.rs
index 4c8b34ae..0c2ccb5f 100644
--- a/src/g1.rs
+++ b/src/g1.rs
@@ -16,6 +16,11 @@ use canonical_derive::Canon;
 #[cfg(feature = "serde_req")]
 use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 
+#[cfg(feature = "rkyv")]
+use rkyv::{
+    out_field, Archive, Deserialize as RkyvDeserialize, Fallible, Serialize as RkyvSerialize,
+};
+
 /// This is an element of $\mathbb{G}_1$ represented in the affine coordinate space.
 /// It is ideal to keep elements in this representation to reduce memory usage and
 /// improve performance through the use of mixed curve model arithmetic.
@@ -29,6 +34,70 @@ pub struct G1Affine {
     infinity: Choice,
 }
 
+#[cfg(feature = "rkyv")]
+#[allow(missing_docs)]
+#[allow(missing_debug_implementations)]
+pub struct ArchivedG1Affine {
+    x: <Fp as Archive>::Archived,
+    y: <Fp as Archive>::Archived,
+    infinity: <u8 as Archive>::Archived,
+}
+
+#[cfg(feature = "rkyv")]
+#[allow(missing_docs)]
+#[allow(missing_debug_implementations)]
+pub struct G1AffineResolver {
+    x: <Fp as Archive>::Resolver,
+    y: <Fp as Archive>::Resolver,
+    infinity: <u8 as Archive>::Resolver,
+}
+
+#[cfg(feature = "rkyv")]
+impl Archive for G1Affine {
+    type Archived = ArchivedG1Affine;
+    type Resolver = G1AffineResolver;
+
+    unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
+        let (fp, fo) = out_field!(out.x);
+        self.x.resolve(pos + fp, resolver.x, fo);
+
+        let (fp, fo) = out_field!(out.y);
+        self.y.resolve(pos + fp, resolver.y, fo);
+
+        let (fp, fo) = out_field!(out.infinity);
+        let infinity = self.infinity.unwrap_u8();
+        #[allow(clippy::unit_arg)]
+        infinity.resolve(pos + fp, resolver.infinity, fo);
+    }
+}
+
+#[cfg(feature = "rkyv")]
+impl<S: Fallible + ?Sized> RkyvSerialize<S> for G1Affine {
+    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
+        let choice = self.infinity.unwrap_u8();
+
+        Ok(Self::Resolver {
+            x: <Fp as RkyvSerialize<S>>::serialize(&self.x, serializer)?,
+            y: <Fp as RkyvSerialize<S>>::serialize(&self.y, serializer)?,
+            infinity: <u8 as RkyvSerialize<S>>::serialize(&choice, serializer)?,
+        })
+    }
+}
+
+#[cfg(feature = "rkyv")]
+impl<D: Fallible + ?Sized> RkyvDeserialize<G1Affine, D> for ArchivedG1Affine {
+    fn deserialize(&self, deserializer: &mut D) -> Result<G1Affine, D::Error> {
+        let infinity = <u8 as RkyvDeserialize<u8, D>>::deserialize(&self.infinity, deserializer)?;
+        let infinity = Choice::from(infinity);
+
+        Ok(G1Affine {
+            x: self.x.deserialize(deserializer)?,
+            y: self.y.deserialize(deserializer)?,
+            infinity,
+        })
+    }
+}
+
 #[cfg(feature = "canon")]
 impl Canon for G1Affine {
     fn encode(&self, sink: &mut Sink) {
@@ -479,6 +548,7 @@ fn endomorphism(p: &G1Affine) -> G1Affine {
 /// This is an element of $\mathbb{G}_1$ represented in the projective coordinate space.
 #[derive(Copy, Clone, Debug)]
 #[cfg_attr(feature = "canon", derive(Canon))]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
 pub struct G1Projective {
     x: Fp,
     y: Fp,
diff --git a/src/g2.rs b/src/g2.rs
index df4bd037..db90c346 100644
--- a/src/g2.rs
+++ b/src/g2.rs
@@ -17,6 +17,11 @@ use canonical_derive::Canon;
 #[cfg(feature = "serde_req")]
 use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 
+#[cfg(feature = "rkyv")]
+use rkyv::{
+    out_field, Archive, Deserialize as RkyvDeserialize, Fallible, Serialize as RkyvSerialize,
+};
+
 /// This is an element of $\mathbb{G}_2$ represented in the affine coordinate space.
 /// It is ideal to keep elements in this representation to reduce memory usage and
 /// improve performance through the use of mixed curve model arithmetic.
@@ -30,6 +35,70 @@ pub struct G2Affine {
     infinity: Choice,
 }
 
+#[cfg(feature = "rkyv")]
+#[allow(missing_docs)]
+#[allow(missing_debug_implementations)]
+pub struct ArchivedG2Affine {
+    x: <Fp2 as Archive>::Archived,
+    y: <Fp2 as Archive>::Archived,
+    infinity: <u8 as Archive>::Archived,
+}
+
+#[cfg(feature = "rkyv")]
+#[allow(missing_docs)]
+#[allow(missing_debug_implementations)]
+pub struct G2AffineResolver {
+    x: <Fp2 as Archive>::Resolver,
+    y: <Fp2 as Archive>::Resolver,
+    infinity: <u8 as Archive>::Resolver,
+}
+
+#[cfg(feature = "rkyv")]
+impl Archive for G2Affine {
+    type Archived = ArchivedG2Affine;
+    type Resolver = G2AffineResolver;
+
+    unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
+        let (fp, fo) = out_field!(out.x);
+        self.x.resolve(pos + fp, resolver.x, fo);
+
+        let (fp, fo) = out_field!(out.y);
+        self.y.resolve(pos + fp, resolver.y, fo);
+
+        let (fp, fo) = out_field!(out.infinity);
+        let infinity = self.infinity.unwrap_u8();
+        #[allow(clippy::unit_arg)]
+        infinity.resolve(pos + fp, resolver.infinity, fo);
+    }
+}
+
+#[cfg(feature = "rkyv")]
+impl<S: Fallible + ?Sized> RkyvSerialize<S> for G2Affine {
+    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
+        let choice = self.infinity.unwrap_u8();
+
+        Ok(Self::Resolver {
+            x: <Fp2 as RkyvSerialize<S>>::serialize(&self.x, serializer)?,
+            y: <Fp2 as RkyvSerialize<S>>::serialize(&self.y, serializer)?,
+            infinity: <u8 as RkyvSerialize<S>>::serialize(&choice, serializer)?,
+        })
+    }
+}
+
+#[cfg(feature = "rkyv")]
+impl<D: Fallible + ?Sized> RkyvDeserialize<G2Affine, D> for ArchivedG2Affine {
+    fn deserialize(&self, deserializer: &mut D) -> Result<G2Affine, D::Error> {
+        let infinity = <u8 as RkyvDeserialize<u8, D>>::deserialize(&self.infinity, deserializer)?;
+        let infinity = Choice::from(infinity);
+
+        Ok(G2Affine {
+            x: self.x.deserialize(deserializer)?,
+            y: self.y.deserialize(deserializer)?,
+            infinity,
+        })
+    }
+}
+
 #[cfg(feature = "canon")]
 impl Canon for G2Affine {
     fn encode(&self, sink: &mut Sink) {
@@ -519,6 +588,7 @@ impl G2Affine {
 /// This is an element of $\mathbb{G}_2$ represented in the projective coordinate space.
 #[derive(Copy, Clone, Debug)]
 #[cfg_attr(feature = "canon", derive(Canon))]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
 pub struct G2Projective {
     pub(crate) x: Fp2,
     pub(crate) y: Fp2,
diff --git a/src/lib.rs b/src/lib.rs
index 93d34707..198a35c0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,6 +48,9 @@ mod scalar;
 pub use scalar::Scalar as BlsScalar;
 pub use scalar::{GENERATOR, ROOT_OF_UNITY, TWO_ADACITY};
 
+#[cfg(feature = "rkyv")]
+pub use scalar::{ArchivedScalar as ArchivedBlsScalar, ScalarResolver as BlsScalarResolver};
+
 #[cfg(feature = "groups")]
 mod fp;
 #[cfg(feature = "groups")]
@@ -59,9 +62,16 @@ mod g2;
 
 #[cfg(feature = "groups")]
 pub use g1::{G1Affine, G1Projective};
+
+#[cfg(all(feature = "groups", feature = "rkyv"))]
+pub use g1::{ArchivedG1Affine, G1AffineResolver};
+
 #[cfg(feature = "groups")]
 pub use g2::{G2Affine, G2Projective};
 
+#[cfg(all(feature = "groups", feature = "rkyv"))]
+pub use g2::{ArchivedG2Affine, G2AffineResolver};
+
 #[cfg(feature = "groups")]
 mod fp12;
 #[cfg(feature = "groups")]
@@ -79,8 +89,14 @@ mod pairings;
 #[cfg(feature = "pairings")]
 pub use pairings::{pairing, Gt, MillerLoopResult};
 
+#[cfg(all(feature = "pairings", feature = "rkyv"))]
+pub use pairings::{ArchivedGt, ArchivedMillerLoopResult, GtResolver, MillerLoopResultResolver};
+
 #[cfg(all(feature = "pairings", feature = "alloc"))]
 pub use pairings::{multi_miller_loop, G2Prepared};
 
+#[cfg(all(feature = "pairings", feature = "rkyv"))]
+pub use pairings::{ArchivedG2Prepared, G2PreparedResolver};
+
 #[cfg(all(feature = "groups", feature = "alloc"))]
 pub mod multiscalar_mul;
diff --git a/src/pairings.rs b/src/pairings.rs
index cfe2f7d0..d49289f6 100644
--- a/src/pairings.rs
+++ b/src/pairings.rs
@@ -15,6 +15,13 @@ use serde::{
     self, de::Visitor, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
 };
 
+#[cfg(feature = "rkyv")]
+use rkyv::{
+    out_field,
+    ser::{ScratchSpace, Serializer as RkyvSerializer},
+    Archive, Deserialize as RkyvDeserialize, Fallible, Serialize as RkyvSerialize,
+};
+
 #[cfg(feature = "alloc")]
 use alloc::vec::Vec;
 
@@ -22,6 +29,7 @@ use alloc::vec::Vec;
 /// of the pairing function. `MillerLoopResult`s cannot be compared with each
 /// other until `.final_exponentiation()` is called, which is also expensive.
 #[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvDeserialize, RkyvSerialize))]
 pub struct MillerLoopResult(pub(crate) Fp12);
 
 impl ConditionallySelectable for MillerLoopResult {
@@ -183,6 +191,7 @@ impl_add_binop_specify_output!(MillerLoopResult, MillerLoopResult, MillerLoopRes
 /// Typically, $\mathbb{G}_T$ is written multiplicatively but we will write it additively to
 /// keep code and abstractions consistent.
 #[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvDeserialize, RkyvSerialize))]
 pub struct Gt(pub(crate) Fp12);
 
 impl ConstantTimeEq for Gt {
@@ -284,8 +293,6 @@ impl<'a, 'b> Mul<&'b BlsScalar> for &'a Gt {
 impl_binops_additive!(Gt, Gt);
 impl_binops_multiplicative!(Gt, BlsScalar);
 
-#[cfg(feature = "alloc")]
-#[derive(Clone, Debug)]
 /// This structure contains cached computations pertaining to a $\mathbb{G}_2$
 /// element as part of the pairing function (specifically, the Miller loop) and
 /// so should be computed whenever a $\mathbb{G}_2$ element is being used in
@@ -294,11 +301,77 @@ impl_binops_multiplicative!(Gt, BlsScalar);
 /// function provided by this crate.
 ///
 /// Requires the `alloc` and `pairing` crate features to be enabled.
+#[cfg(feature = "alloc")]
+#[derive(Clone, Debug)]
+// #[cfg_attr(feature = "rkyv", derive(Archive, RkyvDeserialize, RkyvSerialize))]
 pub struct G2Prepared {
     infinity: Choice,
     coeffs: Vec<(Fp2, Fp2, Fp2)>,
 }
 
+#[cfg(feature = "rkyv")]
+#[allow(missing_docs)]
+#[allow(missing_debug_implementations)]
+pub struct ArchivedG2Prepared {
+    infinity: <u8 as Archive>::Archived,
+    coeffs: <Vec<(Fp2, Fp2, Fp2)> as Archive>::Archived,
+}
+
+#[cfg(feature = "rkyv")]
+#[allow(missing_docs)]
+#[allow(missing_debug_implementations)]
+pub struct G2PreparedResolver {
+    infinity: <u8 as Archive>::Resolver,
+    coeffs: <Vec<(Fp2, Fp2, Fp2)> as Archive>::Resolver,
+}
+
+#[cfg(feature = "rkyv")]
+impl Archive for G2Prepared {
+    type Archived = ArchivedG2Prepared;
+    type Resolver = G2PreparedResolver;
+
+    unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
+        let (fp, fo) = out_field!(out.infinity);
+        let infinity = self.infinity.unwrap_u8();
+        #[allow(clippy::unit_arg)]
+        infinity.resolve(pos + fp, resolver.infinity, fo);
+
+        let (fp, fo) = out_field!(out.coeffs);
+        self.coeffs.resolve(pos + fp, resolver.coeffs, fo);
+    }
+}
+
+#[cfg(feature = "rkyv")]
+impl<S> RkyvSerialize<S> for G2Prepared
+where
+    S: RkyvSerializer + ScratchSpace,
+{
+    fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
+        let choice = self.infinity.unwrap_u8();
+
+        Ok(Self::Resolver {
+            infinity: <u8 as RkyvSerialize<S>>::serialize(&choice, serializer)?,
+            coeffs: <Vec<(Fp2, Fp2, Fp2)> as RkyvSerialize<S>>::serialize(
+                &self.coeffs,
+                serializer,
+            )?,
+        })
+    }
+}
+
+#[cfg(feature = "rkyv")]
+impl<D: Fallible + ?Sized> RkyvDeserialize<G2Prepared, D> for ArchivedG2Prepared {
+    fn deserialize(&self, deserializer: &mut D) -> Result<G2Prepared, D::Error> {
+        let infinity = <u8 as RkyvDeserialize<u8, D>>::deserialize(&self.infinity, deserializer)?;
+        let infinity = Choice::from(infinity);
+
+        Ok(G2Prepared {
+            infinity,
+            coeffs: self.coeffs.deserialize(deserializer)?,
+        })
+    }
+}
+
 #[cfg(feature = "alloc")]
 impl G2Prepared {
     /// Raw bytes representation
diff --git a/src/scalar.rs b/src/scalar.rs
index a6910015..bd349cc7 100644
--- a/src/scalar.rs
+++ b/src/scalar.rs
@@ -16,6 +16,9 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 #[cfg(feature = "serde_req")]
 use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 
+#[cfg(feature = "rkyv")]
+use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
+
 /// Represents an element of the scalar field $\mathbb{F}_q$ of the BLS12-381 elliptic
 /// curve construction.
 // The internal representation of this type is four 64-bit unsigned
@@ -23,6 +26,7 @@ use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 // Montgomery form; i.e., Scalar(a) = aR mod q, with R = 2^256.
 #[derive(Clone, Copy, Eq, Hash, HexDebug)]
 #[cfg_attr(feature = "canon", derive(Canon))]
+#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
 pub struct Scalar(pub [u64; 4]);
 
 impl From<u64> for Scalar {


Test patch: