From 22e79b28c5d17821182a88b652570c37733877b6 Mon Sep 17 00:00:00 2001 From: Arvind Mukund Date: Tue, 31 Oct 2023 17:26:03 -0700 Subject: [PATCH 1/3] p521: Use unsaturated limbs `U576::words()` returns saturated 64-bit limbs but the field arithmetic for tight points require 59-bit limbs, convert it into a little endian byte array and let `fiat-crypto` deal with the unsaturation. Signed-off-by: Arvind Mukund --- p521/src/arithmetic/field.rs | 43 ++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/p521/src/arithmetic/field.rs b/p521/src/arithmetic/field.rs index de625f54..c7343d14 100644 --- a/p521/src/arithmetic/field.rs +++ b/p521/src/arithmetic/field.rs @@ -101,6 +101,41 @@ impl FieldElement { Self::from_uint_unchecked(U576::from_u64(w)) } + /// Converts the words into a byte array to be consumed by [`fiat_p521_from_bytes`] + const fn words_to_le_bytes_unchecked(words: [u64; 9]) -> [u8; 66] { + let mut result: [u8; 66] = [0u8; 66]; + let mut i = 0; + while i < words.len() - 1 { + let word = words[i].to_le_bytes(); + let start = i * 8; + result[start] = word[0]; + result[start + 1] = word[1]; + result[start + 2] = word[2]; + result[start + 3] = word[3]; + result[start + 4] = word[4]; + result[start + 5] = word[5]; + result[start + 6] = word[6]; + result[start + 7] = word[7]; + i += 1; + } + let last_word = words[8].to_le_bytes(); + debug_assert!( + last_word[1] <= 0x1 + && last_word[2] == 0 + && last_word[3] == 0 + && last_word[4] == 0 + && last_word[5] == 0 + && last_word[6] == 0 + && last_word[7] == 0, + "Expected last word to have leading zeroes" + ); + + result[i * 8] = last_word[0]; + result[(i * 8) + 1] = last_word[1]; + + result + } + /// Decode [`FieldElement`] from [`U576`]. /// /// Does *not* perform a check that the field element does not overflow the order. @@ -108,7 +143,9 @@ impl FieldElement { /// Used incorrectly this can lead to invalid results! #[cfg(target_pointer_width = "32")] pub(crate) const fn from_uint_unchecked(w: U576) -> Self { - Self(u32x18_to_u64x9(w.as_words())) + Self(fiat_p521_from_bytes(&Self::words_to_le_bytes_unchecked( + u32x18_to_u64x9(w.as_words()), + ))) } /// Decode [`FieldElement`] from [`U576`]. @@ -118,7 +155,9 @@ impl FieldElement { /// Used incorrectly this can lead to invalid results! #[cfg(target_pointer_width = "64")] pub(crate) const fn from_uint_unchecked(w: U576) -> Self { - Self(w.to_words()) + Self(fiat_p521_from_bytes(&Self::words_to_le_bytes_unchecked( + w.to_words(), + ))) } /// Returns the big-endian encoding of this [`FieldElement`]. From a9472645a93c36fea46573ac18a48b6711dca171 Mon Sep 17 00:00:00 2001 From: Arvind Mukund Date: Tue, 31 Oct 2023 17:42:39 -0700 Subject: [PATCH 2/3] Seperate the asserts for leading zeroes and input bounds Signed-off-by: Arvind Mukund --- p521/src/arithmetic/field.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/p521/src/arithmetic/field.rs b/p521/src/arithmetic/field.rs index c7343d14..18f763be 100644 --- a/p521/src/arithmetic/field.rs +++ b/p521/src/arithmetic/field.rs @@ -120,8 +120,11 @@ impl FieldElement { } let last_word = words[8].to_le_bytes(); debug_assert!( - last_word[1] <= 0x1 - && last_word[2] == 0 + last_word[1] <= 0x1, + "Input bound for the result[65] is [0x0 ~> 0x1]" + ); + debug_assert!( + last_word[2] == 0 && last_word[3] == 0 && last_word[4] == 0 && last_word[5] == 0 From f0299fd0395c1997470d39b8ad1f2eb0c6fecf84 Mon Sep 17 00:00:00 2001 From: Arvind Mukund Date: Tue, 31 Oct 2023 18:07:37 -0700 Subject: [PATCH 3/3] Move into `util` Signed-off-by: Arvind Mukund --- p521/src/arithmetic/field.rs | 58 ++---------------------------------- p521/src/arithmetic/util.rs | 46 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/p521/src/arithmetic/field.rs b/p521/src/arithmetic/field.rs index 18f763be..0a57b35a 100644 --- a/p521/src/arithmetic/field.rs +++ b/p521/src/arithmetic/field.rs @@ -42,8 +42,7 @@ use elliptic_curve::{ Error, FieldBytesEncoding, }; -#[cfg(target_pointer_width = "32")] -use super::util::u32x18_to_u64x9; +use super::util::uint_to_le_bytes_unchecked; /// Constant representing the modulus serialized as hex. /// p = 2^{521} − 1 @@ -101,66 +100,13 @@ impl FieldElement { Self::from_uint_unchecked(U576::from_u64(w)) } - /// Converts the words into a byte array to be consumed by [`fiat_p521_from_bytes`] - const fn words_to_le_bytes_unchecked(words: [u64; 9]) -> [u8; 66] { - let mut result: [u8; 66] = [0u8; 66]; - let mut i = 0; - while i < words.len() - 1 { - let word = words[i].to_le_bytes(); - let start = i * 8; - result[start] = word[0]; - result[start + 1] = word[1]; - result[start + 2] = word[2]; - result[start + 3] = word[3]; - result[start + 4] = word[4]; - result[start + 5] = word[5]; - result[start + 6] = word[6]; - result[start + 7] = word[7]; - i += 1; - } - let last_word = words[8].to_le_bytes(); - debug_assert!( - last_word[1] <= 0x1, - "Input bound for the result[65] is [0x0 ~> 0x1]" - ); - debug_assert!( - last_word[2] == 0 - && last_word[3] == 0 - && last_word[4] == 0 - && last_word[5] == 0 - && last_word[6] == 0 - && last_word[7] == 0, - "Expected last word to have leading zeroes" - ); - - result[i * 8] = last_word[0]; - result[(i * 8) + 1] = last_word[1]; - - result - } - - /// Decode [`FieldElement`] from [`U576`]. - /// - /// Does *not* perform a check that the field element does not overflow the order. - /// - /// Used incorrectly this can lead to invalid results! - #[cfg(target_pointer_width = "32")] - pub(crate) const fn from_uint_unchecked(w: U576) -> Self { - Self(fiat_p521_from_bytes(&Self::words_to_le_bytes_unchecked( - u32x18_to_u64x9(w.as_words()), - ))) - } - /// Decode [`FieldElement`] from [`U576`]. /// /// Does *not* perform a check that the field element does not overflow the order. /// /// Used incorrectly this can lead to invalid results! - #[cfg(target_pointer_width = "64")] pub(crate) const fn from_uint_unchecked(w: U576) -> Self { - Self(fiat_p521_from_bytes(&Self::words_to_le_bytes_unchecked( - w.to_words(), - ))) + Self(fiat_p521_from_bytes(&uint_to_le_bytes_unchecked(w))) } /// Returns the big-endian encoding of this [`FieldElement`]. diff --git a/p521/src/arithmetic/util.rs b/p521/src/arithmetic/util.rs index d11a1eb1..8b6c6168 100644 --- a/p521/src/arithmetic/util.rs +++ b/p521/src/arithmetic/util.rs @@ -1,5 +1,7 @@ //! Utility functions. +use elliptic_curve::bigint::U576; + /// Convert an 18-element array of `u32` into a 9-element array of `u16`, /// assuming integer arrays are in little-endian order. #[cfg(target_pointer_width = "32")] @@ -30,3 +32,47 @@ pub(crate) const fn u64x9_to_u32x18(w: &[u64; 9]) -> [u32; 18] { ret } + +/// Converts the saturated representation [`U576`] into a 528bit array. Each +/// word is copied in little-endian. +pub const fn uint_to_le_bytes_unchecked(w: U576) -> [u8; 66] { + #[cfg(target_pointer_width = "32")] + let words = u32x18_to_u64x9(w.as_words()); + #[cfg(target_pointer_width = "64")] + let words = w.as_words(); + + let mut result: [u8; 66] = [0u8; 66]; + let mut i = 0; + while i < words.len() - 1 { + let word = words[i].to_le_bytes(); + let start = i * 8; + result[start] = word[0]; + result[start + 1] = word[1]; + result[start + 2] = word[2]; + result[start + 3] = word[3]; + result[start + 4] = word[4]; + result[start + 5] = word[5]; + result[start + 6] = word[6]; + result[start + 7] = word[7]; + i += 1; + } + let last_word = words[8].to_le_bytes(); + debug_assert!( + last_word[1] <= 0x1, + "Input bound for the result[65] is [0x0 ~> 0x1]" + ); + debug_assert!( + last_word[2] == 0 + && last_word[3] == 0 + && last_word[4] == 0 + && last_word[5] == 0 + && last_word[6] == 0 + && last_word[7] == 0, + "Expected last word to have leading zeroes" + ); + + result[i * 8] = last_word[0]; + result[(i * 8) + 1] = last_word[1]; + + result +}