diff --git a/src/bls12_381/README.md b/src/bls12_381/README.md index ae968ce..d3811a1 100644 --- a/src/bls12_381/README.md +++ b/src/bls12_381/README.md @@ -55,3 +55,17 @@ y = 1339506544944476473020471379941921221584933875938349620426543736416511423956 x = 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758*u + 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 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. + diff --git a/src/bls12_381/ec.rs b/src/bls12_381/ec.rs index 4350b12..09824c6 100644 --- a/src/bls12_381/ec.rs +++ b/src/bls12_381/ec.rs @@ -586,37 +586,67 @@ pub mod g1 { fn into_affine_unchecked(&self) -> Result { use byteorder::{ReadBytesExt, BigEndian}; - let mut x = FqRepr([0; 6]); - let mut y = FqRepr([0; 6]); + // Create a copy of this representation. + let mut copy = self.0; - { - let mut reader = &self.0[..]; - - for b in x.0.iter_mut().rev() { - *b = reader.read_u64::().unwrap(); - } - - for b in y.0.iter_mut().rev() { - *b = reader.read_u64::().unwrap(); - } - } - - Ok(G1Affine { - x: Fq::from_repr(x)?, - y: Fq::from_repr(y)?, - infinity: false - }) - } - fn from_affine(affine: G1Affine) -> Result { - use byteorder::{WriteBytesExt, BigEndian}; - - if affine.is_zero() { + if copy[0] & (1 << 7) != 0 { + // Distinguisher bit is set, but this should be uncompressed! 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 = ©[..]; + + for b in x.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in y.0.iter_mut().rev() { + *b = reader.read_u64::().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(); - { + 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[..]; 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(()) } - // Determine if the intended y coordinate must be greater - // lexicographically. - let greatest = copy[0] & (1 << 6) != 0; + 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; - // Unset the two most significant bits. - copy[0] &= 0x3f; - - let mut x = FqRepr([0; 6]); - - { - let mut reader = ©[..]; - - for b in x.0.iter_mut().rev() { - *b = reader.read_u64::().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. + if copy.iter().all(|b| *b == 0) { + Ok(G1Affine::zero()) + } else { 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 = ©[..]; + + for b in x.0.iter_mut().rev() { + *b = reader.read_u64::().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 { + fn from_affine(affine: G1Affine) -> Self { use byteorder::{WriteBytesExt, BigEndian}; - if affine.is_zero() { - return Err(()) - } - let mut res = Self::empty(); - { - let mut writer = &mut res.0[..]; + 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[..]; - for digit in affine.x.into_repr().as_ref().iter().rev() { - writer.write_u64::(*digit).unwrap(); + for digit in affine.x.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } } - } - // Distinguish this from an uncompressed element. - res.0[0] |= 1 << 7; // Set highest bit. - - { let mut negy = affine.y; negy.negate(); - // If the correct y coordinate is the largest (lexicographically), - // the bit should be set. + // Set the third most significant bit if the correct y-coordinate + // is lexicographically largest. 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 { use byteorder::{ReadBytesExt, BigEndian}; - let mut x_c1 = FqRepr([0; 6]); - let mut x_c0 = FqRepr([0; 6]); - let mut y_c1 = FqRepr([0; 6]); - let mut y_c0 = FqRepr([0; 6]); + // Create a copy of this representation. + let mut copy = self.0; - { - let mut reader = &self.0[..]; - - for b in x_c1.0.iter_mut().rev() { - *b = reader.read_u64::().unwrap(); - } - - for b in x_c0.0.iter_mut().rev() { - *b = reader.read_u64::().unwrap(); - } - - for b in y_c1.0.iter_mut().rev() { - *b = reader.read_u64::().unwrap(); - } - - for b in y_c0.0.iter_mut().rev() { - *b = reader.read_u64::().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 { - use byteorder::{WriteBytesExt, BigEndian}; - - if affine.is_zero() { + if copy[0] & (1 << 7) != 0 { + // Distinguisher bit is set, but this should be uncompressed! 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 = ©[..]; + + for b in x_c1.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in x_c0.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in y_c1.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in y_c0.0.iter_mut().rev() { + *b = reader.read_u64::().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(); - { + 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[..]; 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(()) } - // Determine if the intended y coordinate must be greater - // lexicographically. - let greatest = copy[0] & (1 << 6) != 0; + 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; - // Unset the two most significant bits. - copy[0] &= 0x3f; - - let mut x_c1 = FqRepr([0; 6]); - let mut x_c0 = FqRepr([0; 6]); - - { - let mut reader = ©[..]; - - for b in x_c1.0.iter_mut().rev() { - *b = reader.read_u64::().unwrap(); - } - - for b in x_c0.0.iter_mut().rev() { - *b = reader.read_u64::().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. + if copy.iter().all(|b| *b == 0) { + Ok(G2Affine::zero()) + } else { 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 = ©[..]; + + for b in x_c1.0.iter_mut().rev() { + *b = reader.read_u64::().unwrap(); + } + + for b in x_c0.0.iter_mut().rev() { + *b = reader.read_u64::().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 { + fn from_affine(affine: G2Affine) -> Self { use byteorder::{WriteBytesExt, BigEndian}; - if affine.is_zero() { - return Err(()) - } - let mut res = Self::empty(); - { - let mut writer = &mut res.0[..]; + 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[..]; - for digit in affine.x.c1.into_repr().as_ref().iter().rev() { - writer.write_u64::(*digit).unwrap(); + for digit in affine.x.c1.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } + + for digit in affine.x.c0.into_repr().as_ref().iter().rev() { + writer.write_u64::(*digit).unwrap(); + } } - for digit in affine.x.c0.into_repr().as_ref().iter().rev() { - writer.write_u64::(*digit).unwrap(); - } - } - - // Distinguish this from an uncompressed element. - res.0[0] |= 1 << 7; // Set highest bit. - - { let mut negy = affine.y; negy.negate(); - // If the correct y coordinate is the largest (lexicographically), - // the bit should be set. + // Set the third most significant bit if the correct y-coordinate + // is lexicographically largest. 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 } } diff --git a/src/bls12_381/tests/g1_compressed_test_vectors.dat b/src/bls12_381/tests/g1_compressed_test_vectors.dat deleted file mode 100644 index 36bf17d..0000000 Binary files a/src/bls12_381/tests/g1_compressed_test_vectors.dat and /dev/null differ diff --git a/src/bls12_381/tests/g1_compressed_valid_test_vectors.dat b/src/bls12_381/tests/g1_compressed_valid_test_vectors.dat new file mode 100644 index 0000000..ea8cd67 Binary files /dev/null and b/src/bls12_381/tests/g1_compressed_valid_test_vectors.dat differ diff --git a/src/bls12_381/tests/g1_uncompressed_invalid_test_vectors.dat b/src/bls12_381/tests/g1_uncompressed_invalid_test_vectors.dat new file mode 100644 index 0000000..e69de29 diff --git a/src/bls12_381/tests/g1_uncompressed_test_vectors.dat b/src/bls12_381/tests/g1_uncompressed_valid_test_vectors.dat similarity index 99% rename from src/bls12_381/tests/g1_uncompressed_test_vectors.dat rename to src/bls12_381/tests/g1_uncompressed_valid_test_vectors.dat index 5194b62..86abfba 100644 Binary files a/src/bls12_381/tests/g1_uncompressed_test_vectors.dat and b/src/bls12_381/tests/g1_uncompressed_valid_test_vectors.dat differ diff --git a/src/bls12_381/tests/g2_compressed_test_vectors.dat b/src/bls12_381/tests/g2_compressed_valid_test_vectors.dat similarity index 66% rename from src/bls12_381/tests/g2_compressed_test_vectors.dat rename to src/bls12_381/tests/g2_compressed_valid_test_vectors.dat index 42b6ee9..a40bbe2 100644 Binary files a/src/bls12_381/tests/g2_compressed_test_vectors.dat and b/src/bls12_381/tests/g2_compressed_valid_test_vectors.dat differ diff --git a/src/bls12_381/tests/g2_uncompressed_test_vectors.dat b/src/bls12_381/tests/g2_uncompressed_valid_test_vectors.dat similarity index 99% rename from src/bls12_381/tests/g2_uncompressed_test_vectors.dat rename to src/bls12_381/tests/g2_uncompressed_valid_test_vectors.dat index 7fcbcc9..92e4bc5 100644 Binary files a/src/bls12_381/tests/g2_uncompressed_test_vectors.dat and b/src/bls12_381/tests/g2_uncompressed_valid_test_vectors.dat differ diff --git a/src/bls12_381/tests/mod.rs b/src/bls12_381/tests/mod.rs index 6baef49..41a54d7 100644 --- a/src/bls12_381/tests/mod.rs +++ b/src/bls12_381/tests/mod.rs @@ -3,14 +3,14 @@ use ::*; fn test_vectors>(expected: &[u8]) { - let mut e = G::one(); + let mut e = G::zero(); let mut v = vec![]; { let mut expected = expected; for _ in 0..1000 { 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()); let mut decoded = E::empty(); @@ -27,22 +27,22 @@ fn test_vectors>(expected: } #[test] -fn test_g1_uncompressed_vectors() { - test_vectors::(include_bytes!("g1_uncompressed_test_vectors.dat")); +fn test_g1_uncompressed_valid_vectors() { + test_vectors::(include_bytes!("g1_uncompressed_valid_test_vectors.dat")); } #[test] -fn test_g1_compressed_vectors() { - test_vectors::(include_bytes!("g1_compressed_test_vectors.dat")); +fn test_g1_compressed_valid_vectors() { + test_vectors::(include_bytes!("g1_compressed_valid_test_vectors.dat")); } #[test] -fn test_g2_uncompressed_vectors() { - test_vectors::(include_bytes!("g2_uncompressed_test_vectors.dat")); +fn test_g2_uncompressed_valid_vectors() { + test_vectors::(include_bytes!("g2_uncompressed_valid_test_vectors.dat")); } #[test] -fn test_g2_compressed_vectors() { - test_vectors::(include_bytes!("g2_compressed_test_vectors.dat")); +fn test_g2_compressed_valid_vectors() { + test_vectors::(include_bytes!("g2_compressed_valid_test_vectors.dat")); } diff --git a/src/lib.rs b/src/lib.rs index 2d600d9..210da01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,13 +184,13 @@ pub trait CurveAffine: Copy + /// Converts this element into its compressed encoding, so long as it's not /// the point at infinity. - fn into_compressed(&self) -> Result { + fn into_compressed(&self) -> Self::Compressed { ::from_affine(*self) } /// Converts this element into its uncompressed encoding, so long as it's not /// the point at infinity. - fn into_uncompressed(&self) -> Result { + fn into_uncompressed(&self) -> Self::Uncompressed { ::from_affine(*self) } } @@ -230,7 +230,7 @@ pub trait EncodedPoint: Sized + /// Creates an `EncodedPoint` from an affine point, as long as the /// point is not the point at infinity. - fn from_affine(affine: Self::Affine) -> Result; + fn from_affine(affine: Self::Affine) -> Self; } /// This trait represents an element of a field. diff --git a/src/tests/curve.rs b/src/tests/curve.rs index ea21f90..e3eb0dc 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -299,25 +299,32 @@ fn random_transformation_tests() { fn random_encoding_tests() { - assert!(G::zero().into_compressed().is_err()); - assert!(G::zero().into_uncompressed().is_err()); - 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 { 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(); assert_eq!(de_uncompressed, r); - let compressed = r.into_compressed().unwrap(); + let compressed = r.into_compressed(); let de_compressed = compressed.into_affine().unwrap(); assert_eq!(de_compressed, r); r.negate(); - let compressed = r.into_compressed().unwrap(); + let compressed = r.into_compressed(); let de_compressed = compressed.into_affine().unwrap(); assert_eq!(de_compressed, r); }