Skip to content
This repository has been archived by the owner on Feb 22, 2024. It is now read-only.

Uncompressed Encodings for Affine Points #2

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ authors = [
"Jack Grigg <jack@electriccoin.co>",
]
edition = "2021"
rust-version = "1.56"
rust-version = "1.60"
license = "MIT OR Apache-2.0"
repository = "https://github.com/zcash/pasta_curves"
documentation = "https://docs.rs/pasta_curves"
Expand Down Expand Up @@ -63,6 +63,8 @@ ec-gpu = { version = "0.2.0", optional = true }
# serde dependencies
serde_crate = { version = "1.0.16", optional = true, default-features = false, features = ["alloc"], package = "serde" }
hex = { version = "0.4", optional = true, default-features = false, features = ["alloc", "serde"] }
paste = "1.0.12"
serde_bytes = "0.11.9"

[features]
default = ["bits", "sqrt-table"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Pallas and Vesta. More details about the Pasta curves can be found

## Minimum Supported Rust Version

Requires Rust **1.56** or higher.
Requires Rust **1.60** or higher.

Minimum supported Rust version can be changed in the future, but it will be done with a
minor version bump.
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "1.56.0"
channel = "1.60.0"
components = [ "clippy", "rustfmt" ]
108 changes: 108 additions & 0 deletions src/curves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,114 @@ macro_rules! new_curve_impl {
}
}

paste::paste! {

/// Uncompressed encoding of the affine representation of a point on the elliptic curve $name.
#[derive(Copy, Clone)]
pub struct [< $name Uncompressed >](pub(crate) [u8; 64]);

impl fmt::Debug for [< $name Uncompressed >] {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0[..].fmt(f)
}
}

impl Default for [< $name Uncompressed >] {
fn default() -> Self {
[< $name Uncompressed >]([0; 64])
}
}

impl AsRef<[u8]> for [< $name Uncompressed >] {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

impl AsMut<[u8]> for [< $name Uncompressed >] {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}

impl ConstantTimeEq for [< $name Uncompressed >] {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}

impl cmp::Eq for [< $name Uncompressed >] {}

impl PartialEq for [< $name Uncompressed >] {
#[inline]
fn eq(&self, other: &Self) -> bool {
bool::from(self.ct_eq(other))
}
}

impl group::UncompressedEncoding for $name_affine{
type Uncompressed = [< $name Uncompressed >];

fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption<Self> {
Self::from_uncompressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_on_curve()))
}

fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption<Self> {
let bytes = &bytes.0;
let infinity_flag_set = Choice::from((bytes[64 - 1] >> 6) & 1);
// Attempt to obtain the x-coordinate
let x = {
let mut tmp = [0; 32];
tmp.copy_from_slice(&bytes[0..32]);
$base::from_repr(tmp)
};

// Attempt to obtain the y-coordinate
let y = {
let mut tmp = [0; 32];
tmp.copy_from_slice(&bytes[32..2*32]);
$base::from_repr(tmp)
};

x.and_then(|x| {
y.and_then(|y| {
// Create a point representing this value
let p = $name_affine::conditional_select(
&$name_affine{
x,
y,
},
&$name_affine::identity(),
infinity_flag_set,
);

CtOption::new(
p,
// If the infinity flag is set, the x and y coordinates should have been zero.
((!infinity_flag_set) | (x.is_zero() & y.is_zero()))
)
})
})
}

fn to_uncompressed(&self) -> Self::Uncompressed {
let mut res = [0; 64];

res[0..32].copy_from_slice(
&$base::conditional_select(&self.x, &$base::zero(), self.is_identity()).to_repr()[..],
);
res[32.. 2*32].copy_from_slice(
&$base::conditional_select(&self.y, &$base::zero(), self.is_identity()).to_repr()[..],
);

res[64 - 1] |= u8::conditional_select(&0u8, &(1u8 << 6), self.is_identity());

[< $name Uncompressed >](res)
}
}

}

impl<'a> From<&'a $name_affine> for $name {
fn from(p: &'a $name_affine) -> $name {
p.to_curve()
Expand Down
185 changes: 183 additions & 2 deletions src/serde_impl.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use core::fmt;
use ff::PrimeField;
use group::GroupEncoding;
use serde_crate::{
de::Error as DeserializeError, Deserialize, Deserializer, Serialize, Serializer,
de::{Error as DeserializeError, SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Deserializer, Serialize, Serializer,
};

use crate::{
curves::{Ep, EpAffine, Eq, EqAffine},
fields::{Fp, Fq},
group::Curve,
EpUncompressed, EqUncompressed,
};

/// Serializes bytes to human readable or compact representation.
Expand Down Expand Up @@ -130,14 +134,89 @@ impl<'de> Deserialize<'de> for Eq {
}
}

struct ByteArrayVisitor {}

impl<'de> Visitor<'de> for ByteArrayVisitor {
type Value = [u8; 64];

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(concat!("an array of length ", 64))
}

fn visit_seq<A>(self, mut seq: A) -> Result<[u8; 64], A::Error>
where
A: SeqAccess<'de>,
{
let mut arr = [u8::default(); 64];
for i in 0..64 {
arr[i] = seq
.next_element()?
.ok_or_else(|| DeserializeError::invalid_length(i, &self))?;
}
Ok(arr)
}
}

impl Serialize for EpUncompressed {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
hex::serde::serialize(self.0, s)
} else {
let mut seq = s.serialize_tuple(64)?;
for elem in self.0 {
seq.serialize_element(&elem)?;
}
seq.end()
}
}
}

impl<'de> Deserialize<'de> for EpUncompressed {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let array = if d.is_human_readable() {
hex::serde::deserialize(d)?
} else {
let visitor = ByteArrayVisitor {};
d.deserialize_tuple(64, visitor)?
};
Ok(Self(array))
}
}

impl Serialize for EqUncompressed {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if s.is_human_readable() {
hex::serde::serialize(self.0, s)
} else {
let mut seq = s.serialize_tuple(64)?;
for elem in self.0 {
seq.serialize_element(&elem)?;
}
seq.end()
}
}
}

impl<'de> Deserialize<'de> for EqUncompressed {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let array = if d.is_human_readable() {
hex::serde::deserialize(d)?
} else {
let visitor = ByteArrayVisitor {};
d.deserialize_tuple(64, visitor)?
};
Ok(Self(array))
}
}

#[cfg(test)]
mod tests {
use super::*;

use core::fmt::Debug;

use ff::Field;
use group::{prime::PrimeCurveAffine, Curve, Group};
use group::{prime::PrimeCurveAffine, Curve, Group, UncompressedEncoding};
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;

Expand Down Expand Up @@ -444,4 +523,106 @@ mod tests {
f
);
}

#[test]
fn serde_ep_uncompressed() {
let mut rng = XorShiftRng::from_seed([
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
0xbc, 0xe5,
]);

for _ in 0..100 {
let f = Ep::random(&mut rng).to_affine().to_uncompressed();
test_roundtrip(&f);
}

let f = Ep::identity().to_affine().to_uncompressed();
test_roundtrip(&f);
assert_eq!(
serde_json::from_slice::<EpUncompressed>(
br#""00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040""#
)
.unwrap(),
f
);
assert_eq!(
bincode::deserialize::<EpUncompressed>(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 64
])
.unwrap(),
f
);

let f = Ep::generator().to_affine().to_uncompressed();
test_roundtrip(&f);
assert_eq!(
serde_json::from_slice::<EpUncompressed>(
br#""00000000ed302d991bf94c09fc984622000000000000000000000000000000400200000000000000000000000000000000000000000000000000000000000000""#
)
.unwrap(),
f
);
assert_eq!(
bincode::deserialize::<EpUncompressed>(&[
0, 0, 0, 0, 237, 48, 45, 153, 27, 249, 76, 9, 252, 152, 70, 34, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
])
.unwrap(),
f
);
}

#[test]
fn serde_eq_uncompressed() {
let mut rng = XorShiftRng::from_seed([
0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
0xbc, 0xe5,
]);

for _ in 0..100 {
let f = Eq::random(&mut rng).to_affine().to_uncompressed();
test_roundtrip(&f);
}

let f = Eq::identity().to_affine().to_uncompressed();
test_roundtrip(&f);
assert_eq!(
serde_json::from_slice::<EqUncompressed>(
br#""00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040""#
)
.unwrap(),
f
);
assert_eq!(
bincode::deserialize::<EqUncompressed>(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 64
])
.unwrap(),
f
);

let f = Eq::generator().to_affine().to_uncompressed();
test_roundtrip(&f);
assert_eq!(
serde_json::from_slice::<EqUncompressed>(
br#""0000000021eb468cdda89409fc984622000000000000000000000000000000400200000000000000000000000000000000000000000000000000000000000000""#
)
.unwrap(),
f
);
assert_eq!(
bincode::deserialize::<EqUncompressed>(&[
0, 0, 0, 0, 33, 235, 70, 140, 221, 168, 148, 9, 252, 152, 70, 34, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
])
.unwrap(),
f
);
}
}