From 723c9762fbb09bf3e82ca9fcb4ed0adc12d11b5a Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:14:00 -0600 Subject: [PATCH] Add field conversion to/from `[u64;4]` (#80) * feat: add field conversion to/from `[u64;4]` * Added conversion tests * Added `montgomery_reduce_short` for no-asm * For bn256, uses assembly conversion when asm feature is on * fix: remove conflict for asm * chore: bump rust-toolchain to 1.67.0 --- rust-toolchain | 2 +- src/bn256/assembly.rs | 8 +++++++ src/bn256/fq.rs | 25 ++++++++++------------ src/bn256/fr.rs | 25 ++++++++++------------ src/derive/field.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ src/secp256k1/fp.rs | 20 ++++++++++-------- src/secp256k1/fq.rs | 20 ++++++++++-------- src/secp256r1/fp.rs | 28 +++++++++++++------------ src/secp256r1/fq.rs | 20 ++++++++++-------- src/tests/field.rs | 16 ++++++++++++++ 10 files changed, 144 insertions(+), 69 deletions(-) diff --git a/rust-toolchain b/rust-toolchain index 7cc6ef41..77c582d8 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.63.0 \ No newline at end of file +1.67.0 \ No newline at end of file diff --git a/src/bn256/assembly.rs b/src/bn256/assembly.rs index a466005a..a8c1984d 100644 --- a/src/bn256/assembly.rs +++ b/src/bn256/assembly.rs @@ -608,6 +608,14 @@ macro_rules! field_arithmetic_asm { $field([r0, r1, r2, r3]) } } + + impl From<$field> for [u64; 4] { + fn from(elt: $field) -> [u64; 4] { + // Turn into canonical form by computing + // (a.R) / R = a + elt.montgomery_reduce_256().0 + } + } }; } diff --git a/src/bn256/fq.rs b/src/bn256/fq.rs index b8e1383a..0e2fc10f 100644 --- a/src/bn256/fq.rs +++ b/src/bn256/fq.rs @@ -3,7 +3,7 @@ use crate::bn256::assembly::field_arithmetic_asm; #[cfg(not(feature = "asm"))] use crate::{field_arithmetic, field_specific}; -use crate::arithmetic::{adc, mac, sbb}; +use crate::arithmetic::{adc, mac, macx, sbb}; use crate::bn256::LegendreSymbol; use crate::ff::{Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use crate::{ @@ -271,20 +271,12 @@ impl ff::PrimeField for Fq { } fn to_repr(&self) -> Self::Repr { - // Turn into canonical form by computing - // (a.R) / R = a - - #[cfg(not(feature = "asm"))] - let tmp = - Self::montgomery_reduce(&[self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]); - #[cfg(feature = "asm")] - let tmp = self.montgomery_reduce_256(); - + let tmp: [u64; 4] = (*self).into(); let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + res[0..8].copy_from_slice(&tmp[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp[3].to_le_bytes()); res } @@ -384,6 +376,11 @@ mod test { crate::tests::field::random_field_tests::("fq".to_string()); } + #[test] + fn test_conversion() { + crate::tests::field::random_conversion_tests::("fq".to_string()); + } + #[test] #[cfg(feature = "bits")] fn test_bits() { diff --git a/src/bn256/fr.rs b/src/bn256/fr.rs index 890c12e8..cd422d4b 100644 --- a/src/bn256/fr.rs +++ b/src/bn256/fr.rs @@ -18,7 +18,7 @@ pub use table::FR_TABLE; #[cfg(not(feature = "bn256-table"))] use crate::impl_from_u64; -use crate::arithmetic::{adc, mac, sbb}; +use crate::arithmetic::{adc, mac, macx, sbb}; use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use crate::{ field_bits, field_common, impl_add_binop_specify_output, impl_binops_additive, @@ -300,20 +300,12 @@ impl ff::PrimeField for Fr { } fn to_repr(&self) -> Self::Repr { - // Turn into canonical form by computing - // (a.R) / R = a - - #[cfg(not(feature = "asm"))] - let tmp = - Self::montgomery_reduce(&[self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]); - #[cfg(feature = "asm")] - let tmp = self.montgomery_reduce_256(); - + let tmp: [u64; 4] = (*self).into(); let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + res[0..8].copy_from_slice(&tmp[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp[3].to_le_bytes()); res } @@ -406,6 +398,11 @@ mod test { ); } + #[test] + fn test_conversion() { + crate::tests::field::random_conversion_tests::("fr".to_string()); + } + #[test] #[cfg(feature = "bits")] fn test_bits() { diff --git a/src/derive/field.rs b/src/derive/field.rs index 0a88556a..945ee981 100644 --- a/src/derive/field.rs +++ b/src/derive/field.rs @@ -267,6 +267,12 @@ macro_rules! field_common { } } + impl From<[u64; 4]> for $field { + fn from(digits: [u64; 4]) -> Self { + Self::from_raw(digits) + } + } + impl From<$field> for [u8; 32] { fn from(value: $field) -> [u8; 32] { value.to_repr() @@ -442,6 +448,49 @@ macro_rules! field_arithmetic { $field([d0 & mask, d1 & mask, d2 & mask, d3 & mask]) } + + /// Montgomery reduce where last 4 registers are 0 + #[inline(always)] + pub(crate) const fn montgomery_reduce_short(r: &[u64; 4]) -> $field { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + let k = r[0].wrapping_mul($inv); + let (_, r0) = macx(r[0], k, $modulus.0[0]); + let (r1, r0) = mac(r[1], k, $modulus.0[1], r0); + let (r2, r0) = mac(r[2], k, $modulus.0[2], r0); + let (r3, r0) = mac(r[3], k, $modulus.0[3], r0); + + let k = r1.wrapping_mul($inv); + let (_, r1) = macx(r1, k, $modulus.0[0]); + let (r2, r1) = mac(r2, k, $modulus.0[1], r1); + let (r3, r1) = mac(r3, k, $modulus.0[2], r1); + let (r0, r1) = mac(r0, k, $modulus.0[3], r1); + + let k = r2.wrapping_mul($inv); + let (_, r2) = macx(r2, k, $modulus.0[0]); + let (r3, r2) = mac(r3, k, $modulus.0[1], r2); + let (r0, r2) = mac(r0, k, $modulus.0[2], r2); + let (r1, r2) = mac(r1, k, $modulus.0[3], r2); + + let k = r3.wrapping_mul($inv); + let (_, r3) = macx(r3, k, $modulus.0[0]); + let (r0, r3) = mac(r0, k, $modulus.0[1], r3); + let (r1, r3) = mac(r1, k, $modulus.0[2], r3); + let (r2, r3) = mac(r2, k, $modulus.0[3], r3); + + // Result may be within MODULUS of the correct value + (&$field([r0, r1, r2, r3])).sub(&$modulus) + } + } + + impl From<$field> for [u64; 4] { + fn from(elt: $field) -> [u64; 4] { + // Turn into canonical form by computing + // (a.R) / R = a + $field::montgomery_reduce_short(&elt.0).0 + } } }; } diff --git a/src/secp256k1/fp.rs b/src/secp256k1/fp.rs index f332f0b6..01fecf84 100644 --- a/src/secp256k1/fp.rs +++ b/src/secp256k1/fp.rs @@ -1,4 +1,4 @@ -use crate::arithmetic::{adc, mac, sbb}; +use crate::arithmetic::{adc, mac, macx, sbb}; use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use crate::{ field_arithmetic, field_bits, field_common, field_specific, impl_add_binop_specify_output, @@ -255,15 +255,12 @@ impl ff::PrimeField for Fp { } fn to_repr(&self) -> Self::Repr { - // Turn into canonical form by computing - // (a.R) / R = a - let tmp = Fp::montgomery_reduce(&[self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]); - + let tmp: [u64; 4] = (*self).into(); let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + res[0..8].copy_from_slice(&tmp[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp[3].to_le_bytes()); res } @@ -353,6 +350,11 @@ mod test { crate::tests::field::random_field_tests::("secp256k1 base".to_string()); } + #[test] + fn test_conversion() { + crate::tests::field::random_conversion_tests::("secp256k1 base".to_string()); + } + #[test] #[cfg(feature = "bits")] fn test_bits() { diff --git a/src/secp256k1/fq.rs b/src/secp256k1/fq.rs index 9c5e7665..d38dc517 100644 --- a/src/secp256k1/fq.rs +++ b/src/secp256k1/fq.rs @@ -1,4 +1,4 @@ -use crate::arithmetic::{adc, mac, sbb}; +use crate::arithmetic::{adc, mac, macx, sbb}; use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use crate::{ field_arithmetic, field_bits, field_common, field_specific, impl_add_binop_specify_output, @@ -266,15 +266,12 @@ impl ff::PrimeField for Fq { } fn to_repr(&self) -> Self::Repr { - // Turn into canonical form by computing - // (a.R) / R = a - let tmp = Fq::montgomery_reduce(&[self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]); - + let tmp: [u64; 4] = (*self).into(); let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + res[0..8].copy_from_slice(&tmp[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp[3].to_le_bytes()); res } @@ -360,6 +357,11 @@ mod test { crate::tests::field::random_field_tests::("secp256k1 scalar".to_string()); } + #[test] + fn test_conversion() { + crate::tests::field::random_conversion_tests::("secp256k1 scalar".to_string()); + } + #[test] #[cfg(feature = "bits")] fn test_bits() { diff --git a/src/secp256r1/fp.rs b/src/secp256r1/fp.rs index d351b64d..e26f19fc 100644 --- a/src/secp256r1/fp.rs +++ b/src/secp256r1/fp.rs @@ -1,4 +1,4 @@ -use crate::arithmetic::{adc, mac, sbb}; +use crate::arithmetic::{adc, mac, macx, sbb}; use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use crate::{ field_arithmetic, field_bits, field_common, field_specific, impl_add_binop_specify_output, @@ -273,15 +273,12 @@ impl ff::PrimeField for Fp { } fn to_repr(&self) -> Self::Repr { - // Turn into canonical form by computing - // (a.R) / R = a - let tmp = Fp::montgomery_reduce(&[self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]); - + let tmp: [u64; 4] = (*self).into(); let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + res[0..8].copy_from_slice(&tmp[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp[3].to_le_bytes()); res } @@ -368,19 +365,24 @@ mod test { #[test] fn test_field() { - crate::tests::field::random_field_tests::("secp256k1 base".to_string()); + crate::tests::field::random_field_tests::("secp256r1 base".to_string()); + } + + #[test] + fn test_conversion() { + crate::tests::field::random_conversion_tests::("secp256r1 base".to_string()); } #[test] #[cfg(feature = "bits")] fn test_bits() { - crate::tests::field::random_bits_tests::("secp256k1 base".to_string()); + crate::tests::field::random_bits_tests::("secp256r1 base".to_string()); } #[test] fn test_serialization() { - crate::tests::field::random_serialization_test::("secp256k1 base".to_string()); + crate::tests::field::random_serialization_test::("secp256r1 base".to_string()); #[cfg(feature = "derive_serde")] - crate::tests::field::random_serde_test::("secp256k1 base".to_string()); + crate::tests::field::random_serde_test::("secp256r1 base".to_string()); } } diff --git a/src/secp256r1/fq.rs b/src/secp256r1/fq.rs index e28c3fe6..05fcf1fa 100644 --- a/src/secp256r1/fq.rs +++ b/src/secp256r1/fq.rs @@ -1,4 +1,4 @@ -use crate::arithmetic::{adc, mac, sbb}; +use crate::arithmetic::{adc, mac, macx, sbb}; use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use core::convert::TryInto; use core::fmt; @@ -262,15 +262,12 @@ impl ff::PrimeField for Fq { } fn to_repr(&self) -> Self::Repr { - // Turn into canonical form by computing - // (a.R) / R = a - let tmp = Fq::montgomery_reduce(&[self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0]); - + let tmp: [u64; 4] = (*self).into(); let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + res[0..8].copy_from_slice(&tmp[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp[3].to_le_bytes()); res } @@ -362,6 +359,11 @@ mod test { crate::tests::field::random_field_tests::("secp256r1 scalar".to_string()); } + #[test] + fn test_conversion() { + crate::tests::field::random_conversion_tests::("secp256r1 scalar".to_string()); + } + #[test] fn test_serialization() { crate::tests::field::random_serialization_test::("secp256r1 scalar".to_string()); diff --git a/src/tests/field.rs b/src/tests/field.rs index a85b3f0e..a064441e 100644 --- a/src/tests/field.rs +++ b/src/tests/field.rs @@ -212,6 +212,22 @@ fn random_expansion_tests(mut rng: R, type_name: String) { end_timer!(start); } +pub fn random_conversion_tests>(type_name: String) { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, + 0xe5, + ]); + let _message = format!("conversion {type_name}"); + let start = start_timer!(|| _message); + for _ in 0..1000000 { + let a = F::random(&mut rng); + let bytes = a.to_repr(); + let b = F::from_repr(bytes).unwrap(); + assert_eq!(a, b); + } + end_timer!(start); +} + #[cfg(feature = "bits")] pub fn random_bits_tests(type_name: String) { let mut rng = XorShiftRng::from_seed([