Implements and documents serialization, closes #11.

This commit is contained in:
Sean Bowe 2017-07-16 20:52:00 -06:00
parent e72660056e
commit c618240c91
No known key found for this signature in database
GPG Key ID: 95684257D8F8B031
11 changed files with 326 additions and 219 deletions

@ -55,3 +55,17 @@ y = 1339506544944476473020471379941921221584933875938349620426543736416511423956
x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160
y = 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582*u + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 y = 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582*u + 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905
``` ```
### Serialization
* Fq elements are encoded in big-endian form. They occupy 48 bytes in this form.
* Fq2 elements are encoded in big-endian form, meaning that the Fq element c0 + c1 * u is represented by the Fq element c1 followed by the Fq element c0. This means Fq2 elements occupy 96 bytes in this form.
* The group G1 uses Fq elements for coordinates. The group G2 uses Fq2 elements for coordinates.
* G1 and G2 elements can be encoded in uncompressed form (the x-coordinate followed by the y-coordinate) or in compressed form (just the x-coordinate). G1 elements occupy 96 bytes in uncompressed form, and 48 bytes in compressed form. G2 elements occupy 192 bytes in uncompressed form, and 96 bytes in compressed form.
The most-significant three bits of a G1 or G2 encoding should be masked away before the coordinate(s) are interpreted. These bits are used to unambiguously represent the underlying element:
* The most significant bit, when set, indicates that the point is in compressed form. Otherwise, the point is in uncompressed form.
* The second-most significant bit indicates that the point is at infinity. If this bit is set, the remaining bits of the group element's encoding should be set to zero.
* The third-most significant bit is set if (and only if) this point is in compressed form _and_ it is not the point at infinity _and_ its y-coordinate is the lexicographically largest of the two associated with the encoded x-coordinate.

@ -586,37 +586,67 @@ pub mod g1 {
fn into_affine_unchecked(&self) -> Result<G1Affine, ()> { fn into_affine_unchecked(&self) -> Result<G1Affine, ()> {
use byteorder::{ReadBytesExt, BigEndian}; use byteorder::{ReadBytesExt, BigEndian};
let mut x = FqRepr([0; 6]); // Create a copy of this representation.
let mut y = FqRepr([0; 6]); let mut copy = self.0;
{ if copy[0] & (1 << 7) != 0 {
let mut reader = &self.0[..]; // Distinguisher bit is set, but this should be uncompressed!
for b in x.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in y.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
Ok(G1Affine {
x: Fq::from_repr(x)?,
y: Fq::from_repr(y)?,
infinity: false
})
}
fn from_affine(affine: G1Affine) -> Result<Self, ()> {
use byteorder::{WriteBytesExt, BigEndian};
if affine.is_zero() {
return Err(()) return Err(())
} }
if copy[0] & (1 << 6) != 0 {
// This is the point at infinity, which means that if we mask away
// the first two bits, the entire representation should consist
// of zeroes.
copy[0] &= 0x3f;
if copy.iter().all(|b| *b == 0) {
Ok(G1Affine::zero())
} else {
Err(())
}
} else {
if copy[0] & (1 << 5) != 0 {
// The bit indicating the y-coordinate should be lexicographically
// largest is set, but this is an uncompressed element.
return Err(())
}
// Unset the three most significant bits.
copy[0] &= 0x1f;
let mut x = FqRepr([0; 6]);
let mut y = FqRepr([0; 6]);
{
let mut reader = &copy[..];
for b in x.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in y.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
Ok(G1Affine {
x: Fq::from_repr(x)?,
y: Fq::from_repr(y)?,
infinity: false
})
}
}
fn from_affine(affine: G1Affine) -> Self {
use byteorder::{WriteBytesExt, BigEndian};
let mut res = Self::empty(); let mut res = Self::empty();
{ if affine.is_zero() {
// Set the second-most significant bit to indicate this point
// is at infinity.
res.0[0] |= 1 << 6;
} else {
let mut writer = &mut res.0[..]; let mut writer = &mut res.0[..];
for digit in affine.x.into_repr().as_ref().iter().rev() { for digit in affine.x.into_repr().as_ref().iter().rev() {
@ -628,7 +658,7 @@ pub mod g1 {
} }
} }
Ok(res) res
} }
} }
@ -662,85 +692,98 @@ pub mod g1 {
return Err(()) return Err(())
} }
// Determine if the intended y coordinate must be greater if copy[0] & (1 << 6) != 0 {
// lexicographically. // This is the point at infinity, which means that if we mask away
let greatest = copy[0] & (1 << 6) != 0; // the first two bits, the entire representation should consist
// of zeroes.
copy[0] &= 0x3f;
// Unset the two most significant bits. if copy.iter().all(|b| *b == 0) {
copy[0] &= 0x3f; Ok(G1Affine::zero())
} else {
let mut x = FqRepr([0; 6]);
{
let mut reader = &copy[..];
for b in x.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
// Interpret as Fq element.
let x = Fq::from_repr(x)?;
// Compute x^3 + b
let mut x3b = x;
x3b.square();
x3b.mul_assign(&x);
x3b.add_assign(&G1Affine::get_coeff_b());
// Attempt to compute y
match x3b.sqrt() {
Some(y) => {
let mut negy = y;
negy.negate();
// Get the parity of the sqrt we found.
let parity = y > negy;
Ok(G1Affine {
x: x,
y: if parity == greatest { y } else { negy },
infinity: false
})
},
None => {
// Point must not be on the curve.
Err(()) Err(())
} }
} else {
// Determine if the intended y coordinate must be greater
// lexicographically.
let greatest = copy[0] & (1 << 5) != 0;
// Unset the three most significant bits.
copy[0] &= 0x1f;
let mut x = FqRepr([0; 6]);
{
let mut reader = &copy[..];
for b in x.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
// Interpret as Fq element.
let x = Fq::from_repr(x)?;
// Compute x^3 + b
let mut x3b = x;
x3b.square();
x3b.mul_assign(&x);
x3b.add_assign(&G1Affine::get_coeff_b());
// Attempt to compute y
match x3b.sqrt() {
Some(y) => {
let mut negy = y;
negy.negate();
// Get the parity of the sqrt we found.
let parity = y > negy;
Ok(G1Affine {
x: x,
y: if parity == greatest { y } else { negy },
infinity: false
})
},
None => {
// Point must not be on the curve.
Err(())
}
}
} }
} }
fn from_affine(affine: G1Affine) -> Result<Self, ()> { fn from_affine(affine: G1Affine) -> Self {
use byteorder::{WriteBytesExt, BigEndian}; use byteorder::{WriteBytesExt, BigEndian};
if affine.is_zero() {
return Err(())
}
let mut res = Self::empty(); let mut res = Self::empty();
{ if affine.is_zero() {
let mut writer = &mut res.0[..]; // Set the second-most significant bit to indicate this point
// is at infinity.
res.0[0] |= 1 << 6;
} else {
{
let mut writer = &mut res.0[..];
for digit in affine.x.into_repr().as_ref().iter().rev() { for digit in affine.x.into_repr().as_ref().iter().rev() {
writer.write_u64::<BigEndian>(*digit).unwrap(); writer.write_u64::<BigEndian>(*digit).unwrap();
}
} }
}
// Distinguish this from an uncompressed element.
res.0[0] |= 1 << 7; // Set highest bit.
{
let mut negy = affine.y; let mut negy = affine.y;
negy.negate(); negy.negate();
// If the correct y coordinate is the largest (lexicographically), // Set the third most significant bit if the correct y-coordinate
// the bit should be set. // is lexicographically largest.
if affine.y > negy { if affine.y > negy {
res.0[0] |= 1 << 6; // Set second highest bit. res.0[0] |= 1 << 5;
} }
} }
Ok(res) // Set highest bit to distinguish this as a compressed element.
res.0[0] |= 1 << 7;
res
} }
} }
@ -1080,53 +1123,83 @@ pub mod g2 {
fn into_affine_unchecked(&self) -> Result<G2Affine, ()> { fn into_affine_unchecked(&self) -> Result<G2Affine, ()> {
use byteorder::{ReadBytesExt, BigEndian}; use byteorder::{ReadBytesExt, BigEndian};
let mut x_c1 = FqRepr([0; 6]); // Create a copy of this representation.
let mut x_c0 = FqRepr([0; 6]); let mut copy = self.0;
let mut y_c1 = FqRepr([0; 6]);
let mut y_c0 = FqRepr([0; 6]);
{ if copy[0] & (1 << 7) != 0 {
let mut reader = &self.0[..]; // Distinguisher bit is set, but this should be uncompressed!
for b in x_c1.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in x_c0.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in y_c1.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in y_c0.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
Ok(G2Affine {
x: Fq2 {
c0: Fq::from_repr(x_c0)?,
c1: Fq::from_repr(x_c1)?
},
y: Fq2 {
c0: Fq::from_repr(y_c0)?,
c1: Fq::from_repr(y_c1)?
},
infinity: false
})
}
fn from_affine(affine: G2Affine) -> Result<Self, ()> {
use byteorder::{WriteBytesExt, BigEndian};
if affine.is_zero() {
return Err(()) return Err(())
} }
if copy[0] & (1 << 6) != 0 {
// This is the point at infinity, which means that if we mask away
// the first two bits, the entire representation should consist
// of zeroes.
copy[0] &= 0x3f;
if copy.iter().all(|b| *b == 0) {
Ok(G2Affine::zero())
} else {
Err(())
}
} else {
if copy[0] & (1 << 5) != 0 {
// The bit indicating the y-coordinate should be lexicographically
// largest is set, but this is an uncompressed element.
return Err(())
}
// Unset the three most significant bits.
copy[0] &= 0x1f;
let mut x_c0 = FqRepr([0; 6]);
let mut x_c1 = FqRepr([0; 6]);
let mut y_c0 = FqRepr([0; 6]);
let mut y_c1 = FqRepr([0; 6]);
{
let mut reader = &copy[..];
for b in x_c1.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in x_c0.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in y_c1.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in y_c0.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
Ok(G2Affine {
x: Fq2 {
c0: Fq::from_repr(x_c0)?,
c1: Fq::from_repr(x_c1)?
},
y: Fq2 {
c0: Fq::from_repr(y_c0)?,
c1: Fq::from_repr(y_c1)?
},
infinity: false
})
}
}
fn from_affine(affine: G2Affine) -> Self {
use byteorder::{WriteBytesExt, BigEndian};
let mut res = Self::empty(); let mut res = Self::empty();
{ if affine.is_zero() {
// Set the second-most significant bit to indicate this point
// is at infinity.
res.0[0] |= 1 << 6;
} else {
let mut writer = &mut res.0[..]; let mut writer = &mut res.0[..];
for digit in affine.x.c1.into_repr().as_ref().iter().rev() { for digit in affine.x.c1.into_repr().as_ref().iter().rev() {
@ -1146,7 +1219,7 @@ pub mod g2 {
} }
} }
Ok(res) res
} }
} }
@ -1180,97 +1253,110 @@ pub mod g2 {
return Err(()) return Err(())
} }
// Determine if the intended y coordinate must be greater if copy[0] & (1 << 6) != 0 {
// lexicographically. // This is the point at infinity, which means that if we mask away
let greatest = copy[0] & (1 << 6) != 0; // the first two bits, the entire representation should consist
// of zeroes.
copy[0] &= 0x3f;
// Unset the two most significant bits. if copy.iter().all(|b| *b == 0) {
copy[0] &= 0x3f; Ok(G2Affine::zero())
} else {
let mut x_c1 = FqRepr([0; 6]);
let mut x_c0 = FqRepr([0; 6]);
{
let mut reader = &copy[..];
for b in x_c1.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in x_c0.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
// Interpret as Fq element.
let x = Fq2 {
c0: Fq::from_repr(x_c0)?,
c1: Fq::from_repr(x_c1)?
};
// Compute x^3 + b
let mut x3b = x;
x3b.square();
x3b.mul_assign(&x);
x3b.add_assign(&G2Affine::get_coeff_b());
// Attempt to compute y
match x3b.sqrt() {
Some(y) => {
let mut negy = y;
negy.negate();
// Get the parity of the sqrt we found.
let parity = y > negy;
Ok(G2Affine {
x: x,
y: if parity == greatest { y } else { negy },
infinity: false
})
},
None => {
// Point must not be on the curve.
Err(()) Err(())
} }
} else {
// Determine if the intended y coordinate must be greater
// lexicographically.
let greatest = copy[0] & (1 << 5) != 0;
// Unset the three most significant bits.
copy[0] &= 0x1f;
let mut x_c1 = FqRepr([0; 6]);
let mut x_c0 = FqRepr([0; 6]);
{
let mut reader = &copy[..];
for b in x_c1.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
for b in x_c0.0.iter_mut().rev() {
*b = reader.read_u64::<BigEndian>().unwrap();
}
}
// Interpret as Fq element.
let x = Fq2 {
c0: Fq::from_repr(x_c0)?,
c1: Fq::from_repr(x_c1)?
};
// Compute x^3 + b
let mut x3b = x;
x3b.square();
x3b.mul_assign(&x);
x3b.add_assign(&G2Affine::get_coeff_b());
// Attempt to compute y
match x3b.sqrt() {
Some(y) => {
let mut negy = y;
negy.negate();
// Get the parity of the sqrt we found.
let parity = y > negy;
Ok(G2Affine {
x: x,
y: if parity == greatest { y } else { negy },
infinity: false
})
},
None => {
// Point must not be on the curve.
Err(())
}
}
} }
} }
fn from_affine(affine: G2Affine) -> Result<Self, ()> { fn from_affine(affine: G2Affine) -> Self {
use byteorder::{WriteBytesExt, BigEndian}; use byteorder::{WriteBytesExt, BigEndian};
if affine.is_zero() {
return Err(())
}
let mut res = Self::empty(); let mut res = Self::empty();
{ if affine.is_zero() {
let mut writer = &mut res.0[..]; // Set the second-most significant bit to indicate this point
// is at infinity.
res.0[0] |= 1 << 6;
} else {
{
let mut writer = &mut res.0[..];
for digit in affine.x.c1.into_repr().as_ref().iter().rev() { for digit in affine.x.c1.into_repr().as_ref().iter().rev() {
writer.write_u64::<BigEndian>(*digit).unwrap(); writer.write_u64::<BigEndian>(*digit).unwrap();
}
for digit in affine.x.c0.into_repr().as_ref().iter().rev() {
writer.write_u64::<BigEndian>(*digit).unwrap();
}
} }
for digit in affine.x.c0.into_repr().as_ref().iter().rev() {
writer.write_u64::<BigEndian>(*digit).unwrap();
}
}
// Distinguish this from an uncompressed element.
res.0[0] |= 1 << 7; // Set highest bit.
{
let mut negy = affine.y; let mut negy = affine.y;
negy.negate(); negy.negate();
// If the correct y coordinate is the largest (lexicographically), // Set the third most significant bit if the correct y-coordinate
// the bit should be set. // is lexicographically largest.
if affine.y > negy { if affine.y > negy {
res.0[0] |= 1 << 6; // Set second highest bit. res.0[0] |= 1 << 5;
} }
} }
Ok(res) // Set highest bit to distinguish this as a compressed element.
res.0[0] |= 1 << 7;
res
} }
} }

@ -3,14 +3,14 @@ use ::*;
fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: &[u8]) fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected: &[u8])
{ {
let mut e = G::one(); let mut e = G::zero();
let mut v = vec![]; let mut v = vec![];
{ {
let mut expected = expected; let mut expected = expected;
for _ in 0..1000 { for _ in 0..1000 {
let e_affine = e.into_affine(); let e_affine = e.into_affine();
let encoded = E::from_affine(e_affine).unwrap(); let encoded = E::from_affine(e_affine);
v.extend_from_slice(encoded.as_ref()); v.extend_from_slice(encoded.as_ref());
let mut decoded = E::empty(); let mut decoded = E::empty();
@ -27,22 +27,22 @@ fn test_vectors<G: CurveProjective, E: EncodedPoint<Affine=G::Affine>>(expected:
} }
#[test] #[test]
fn test_g1_uncompressed_vectors() { fn test_g1_uncompressed_valid_vectors() {
test_vectors::<G1, G1Uncompressed>(include_bytes!("g1_uncompressed_test_vectors.dat")); test_vectors::<G1, G1Uncompressed>(include_bytes!("g1_uncompressed_valid_test_vectors.dat"));
} }
#[test] #[test]
fn test_g1_compressed_vectors() { fn test_g1_compressed_valid_vectors() {
test_vectors::<G1, G1Compressed>(include_bytes!("g1_compressed_test_vectors.dat")); test_vectors::<G1, G1Compressed>(include_bytes!("g1_compressed_valid_test_vectors.dat"));
} }
#[test] #[test]
fn test_g2_uncompressed_vectors() { fn test_g2_uncompressed_valid_vectors() {
test_vectors::<G2, G2Uncompressed>(include_bytes!("g2_uncompressed_test_vectors.dat")); test_vectors::<G2, G2Uncompressed>(include_bytes!("g2_uncompressed_valid_test_vectors.dat"));
} }
#[test] #[test]
fn test_g2_compressed_vectors() { fn test_g2_compressed_valid_vectors() {
test_vectors::<G2, G2Compressed>(include_bytes!("g2_compressed_test_vectors.dat")); test_vectors::<G2, G2Compressed>(include_bytes!("g2_compressed_valid_test_vectors.dat"));
} }

@ -184,13 +184,13 @@ pub trait CurveAffine: Copy +
/// Converts this element into its compressed encoding, so long as it's not /// Converts this element into its compressed encoding, so long as it's not
/// the point at infinity. /// the point at infinity.
fn into_compressed(&self) -> Result<Self::Compressed, ()> { fn into_compressed(&self) -> Self::Compressed {
<Self::Compressed as EncodedPoint>::from_affine(*self) <Self::Compressed as EncodedPoint>::from_affine(*self)
} }
/// Converts this element into its uncompressed encoding, so long as it's not /// Converts this element into its uncompressed encoding, so long as it's not
/// the point at infinity. /// the point at infinity.
fn into_uncompressed(&self) -> Result<Self::Uncompressed, ()> { fn into_uncompressed(&self) -> Self::Uncompressed {
<Self::Uncompressed as EncodedPoint>::from_affine(*self) <Self::Uncompressed as EncodedPoint>::from_affine(*self)
} }
} }
@ -230,7 +230,7 @@ pub trait EncodedPoint: Sized +
/// Creates an `EncodedPoint` from an affine point, as long as the /// Creates an `EncodedPoint` from an affine point, as long as the
/// point is not the point at infinity. /// point is not the point at infinity.
fn from_affine(affine: Self::Affine) -> Result<Self, ()>; fn from_affine(affine: Self::Affine) -> Self;
} }
/// This trait represents an element of a field. /// This trait represents an element of a field.

@ -299,25 +299,32 @@ fn random_transformation_tests<G: CurveProjective>() {
fn random_encoding_tests<G: CurveAffine>() fn random_encoding_tests<G: CurveAffine>()
{ {
assert!(G::zero().into_compressed().is_err());
assert!(G::zero().into_uncompressed().is_err());
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]); let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
assert_eq!(
G::zero().into_uncompressed().into_affine().unwrap(),
G::zero()
);
assert_eq!(
G::zero().into_compressed().into_affine().unwrap(),
G::zero()
);
for _ in 0..1000 { for _ in 0..1000 {
let mut r = G::Projective::rand(&mut rng).into_affine(); let mut r = G::Projective::rand(&mut rng).into_affine();
let uncompressed = r.into_uncompressed().unwrap(); let uncompressed = r.into_uncompressed();
let de_uncompressed = uncompressed.into_affine().unwrap(); let de_uncompressed = uncompressed.into_affine().unwrap();
assert_eq!(de_uncompressed, r); assert_eq!(de_uncompressed, r);
let compressed = r.into_compressed().unwrap(); let compressed = r.into_compressed();
let de_compressed = compressed.into_affine().unwrap(); let de_compressed = compressed.into_affine().unwrap();
assert_eq!(de_compressed, r); assert_eq!(de_compressed, r);
r.negate(); r.negate();
let compressed = r.into_compressed().unwrap(); let compressed = r.into_compressed();
let de_compressed = compressed.into_affine().unwrap(); let de_compressed = compressed.into_affine().unwrap();
assert_eq!(de_compressed, r); assert_eq!(de_compressed, r);
} }