diff --git a/.travis.yml b/.travis.yml index 709f428..f9f46f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ rust: env: - TEST_COMMAND=test FEATURES=--features="yolocrypto" + - TEST_COMMAND=test FEATURES=--features="yolocrypto serde" - TEST_COMMAND=test FEATURES=--features="yolocrypto nightly" - TEST_COMMAND=bench FEATURES=--features="yolocrypto bench" - TEST_COMMAND=bench FEATURES=--features="yolocrypto nightly bench" diff --git a/Cargo.toml b/Cargo.toml index 1e06fd9..5bdd3bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,10 @@ exclude = [ [badges] travis-ci = { repository = "isislovecruft/curve25519-dalek", branch = "master"} +[dependencies.serde] +version = "1.0" +optional = true + [dependencies.arrayref] version = "0.3.3" @@ -35,6 +39,9 @@ version = "^0.6" [dev-dependencies.sha2] version = "0.4" +[dev-dependencies.serde_cbor] +version = "0.6" + [features] nightly = ["radix_51"] default = ["std"] diff --git a/src/curve.rs b/src/curve.rs index 276024c..870d82d 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -278,6 +278,59 @@ impl CompressedMontgomeryU { } } +// ------------------------------------------------------------------------ +// Serde support +// ------------------------------------------------------------------------ +// Serializes to and from `ExtendedPoint` directly, doing compression +// and decompression internally. This means that users can create +// structs containing `ExtendedPoint`s and use Serde's derived +// serializers to serialize those structures. + +#[cfg(feature = "serde")] +use serde::{self, Serialize, Deserialize, Serializer, Deserializer}; +#[cfg(feature = "serde")] +use serde::de::Visitor; + +#[cfg(feature = "serde")] +impl Serialize for ExtendedPoint { + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + serializer.serialize_bytes(self.compress_edwards().as_bytes()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for ExtendedPoint { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + struct ExtendedPointVisitor; + + impl<'de> Visitor<'de> for ExtendedPointVisitor { + type Value = ExtendedPoint; + + fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + formatter.write_str("a valid point in Edwards y + sign format") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where E: serde::de::Error + { + if v.len() == 32 { + let arr32 = array_ref!(v,0,32); // &[u8;32] from &[u8] + CompressedEdwardsY(*arr32).decompress() + .ok_or(serde::de::Error::custom("decompression failed")) + } else { + Err(serde::de::Error::invalid_length(v.len(), &self)) + } + } + } + + deserializer.deserialize_bytes(ExtendedPointVisitor) + } +} + // ------------------------------------------------------------------------ // Internal point representations // ------------------------------------------------------------------------ @@ -1558,7 +1611,7 @@ mod test { mod vartime { use super::super::*; use super::{A_SCALAR, B_SCALAR, A_TIMES_BASEPOINT, DOUBLE_SCALAR_MULT_RESULT}; - + /// Test double_scalar_mult_vartime vs ed25519.py #[test] fn double_scalar_mult_basepoint_vs_ed25519py() { @@ -1577,6 +1630,28 @@ mod test { assert_eq!(result.compress_edwards(), DOUBLE_SCALAR_MULT_RESULT); } } + + #[cfg(feature = "serde")] + use serde_cbor; + + #[test] + #[cfg(feature = "serde")] + fn serde_cbor_basepoint_roundtrip() { + let output = serde_cbor::to_vec(&constants::ED25519_BASEPOINT).unwrap(); + let parsed: ExtendedPoint = serde_cbor::from_slice(&output).unwrap(); + assert_eq!(parsed.compress_edwards(), constants::BASE_CMPRSSD); + } + + #[test] + #[cfg(feature = "serde")] + fn serde_cbor_decode_invalid_fails() { + let mut output = serde_cbor::to_vec(&constants::ED25519_BASEPOINT).unwrap(); + // CBOR apparently has two bytes of overhead for a 32-byte string. + // Set the low byte of the compressed point to 1 to make it invalid. + output[2] = 1; + let parsed: Result = serde_cbor::from_slice(&output); + assert!(parsed.is_err()); + } } // ------------------------------------------------------------------------ diff --git a/src/decaf.rs b/src/decaf.rs index 4106e70..07c24c6 100644 --- a/src/decaf.rs +++ b/src/decaf.rs @@ -116,6 +116,63 @@ impl Identity for CompressedDecaf { } } +// ------------------------------------------------------------------------ +// Serde support +// ------------------------------------------------------------------------ +// Serializes to and from `DecafPoint` directly, doing compression +// and decompression internally. This means that users can create +// structs containing `DecafPoint`s and use Serde's derived +// serializers to serialize those structures. + +#[cfg(feature = "serde")] +use serde::{self, Serialize, Deserialize, Serializer, Deserializer}; +#[cfg(feature = "serde")] +use serde::de::Visitor; + +#[cfg(feature = "serde")] +impl Serialize for DecafPoint { + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + serializer.serialize_bytes(self.compress().as_bytes()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for DecafPoint { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + struct DecafPointVisitor; + + impl<'de> Visitor<'de> for DecafPointVisitor { + type Value = DecafPoint; + + fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + formatter.write_str("a valid point in Decaf format") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where E: serde::de::Error + { + if v.len() == 32 { + let arr32 = array_ref!(v,0,32); // &[u8;32] from &[u8] + CompressedDecaf(*arr32).decompress() + .ok_or(serde::de::Error::custom("decompression failed")) + } else { + Err(serde::de::Error::invalid_length(v.len(), &self)) + } + } + } + + deserializer.deserialize_bytes(DecafPointVisitor) + } +} + +// ------------------------------------------------------------------------ +// Internal point representations +// ------------------------------------------------------------------------ + /// A point in a prime-order group. /// /// XXX think about how this API should work @@ -523,6 +580,18 @@ mod test { use curve::Identity; use super::*; + #[cfg(feature = "serde")] + use serde_cbor; + + #[test] + #[cfg(feature = "serde")] + fn serde_cbor_basepoint_roundtrip() { + let output = serde_cbor::to_vec(&constants::DECAF_ED25519_BASEPOINT).unwrap(); + let parsed: DecafPoint = serde_cbor::from_slice(&output).unwrap(); + assert_eq!(parsed, constants::DECAF_ED25519_BASEPOINT); + } + + #[test] fn decaf_decompress_negative_s_fails() { // constants::d is neg, so decompression should fail as |d| != d. diff --git a/src/lib.rs b/src/lib.rs index 40efe8b..408d7ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,11 @@ extern crate arrayref; extern crate generic_array; extern crate digest; +#[cfg(feature = "serde")] +extern crate serde; +#[cfg(all(test, feature = "serde"))] +extern crate serde_cbor; + #[cfg(feature = "std")] extern crate core; diff --git a/src/scalar.rs b/src/scalar.rs index 34a45ba..7f90624 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -185,6 +185,50 @@ impl CTAssignable for Scalar { } } +#[cfg(feature = "serde")] +use serde::{self, Serialize, Deserialize, Serializer, Deserializer}; +#[cfg(feature = "serde")] +use serde::de::Visitor; + +#[cfg(feature = "serde")] +impl Serialize for Scalar { + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + serializer.serialize_bytes(self.as_bytes()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Scalar { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + struct ScalarVisitor; + + impl<'de> Visitor<'de> for ScalarVisitor { + type Value = Scalar; + + fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + formatter.write_str("a 32-byte scalar value") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where E: serde::de::Error + { + if v.len() == 32 { + // array_ref turns &[u8] into &[u8;32] + Ok(Scalar(*array_ref!(v,0,32))) + } else { + Err(serde::de::Error::invalid_length(v.len(), &self)) + } + } + } + + deserializer.deserialize_bytes(ScalarVisitor) + } +} + impl Scalar { /// Return a `Scalar` chosen uniformly at random using a user-provided RNG. /// @@ -827,6 +871,17 @@ mod test { assert_eq!(should_be_X, X); } + + #[cfg(feature = "serde")] + use serde_cbor; + + #[test] + #[cfg(feature = "serde")] + fn serde_cbor_scalar_roundtrip() { + let output = serde_cbor::to_vec(&X).unwrap(); + let parsed: Scalar = serde_cbor::from_slice(&output).unwrap(); + assert_eq!(parsed, X); + } } #[cfg(all(test, feature = "bench"))]