Skip to content

Commit

Permalink
Add PrimeField::from_bytes_mod_order (#164)
Browse files Browse the repository at this point in the history
* Add PrimeField::from_bytes_mod_order
  • Loading branch information
ValarDragon authored Jan 9, 2021
1 parent 1a5c6b5 commit 12afad6
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The main features of this release are:
- #96 (ark-ff) Make the `field_new` macro accept values in integer form, without requiring decomposition into limbs, and without requiring encoding in Montgomery form.
- #106 (ark-ff, ark-ec) Add `Zeroize` trait bound to `Field, ProjectiveGroup, AffineGroup` traits.
- #117 (ark-poly) Add operations to `SparsePolynomial`, so it implements `Polynomial`
- #164 (ark-ff) Add methods `from_{be, le}_bytes_mod_order` to the `PrimeField` trait.

### Improvements
- #22 (ark-ec) Speedup fixed-base MSMs
Expand Down
1 change: 1 addition & 0 deletions ff/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ rustc_version = "0.3"

[dev-dependencies]
rand_xorshift = "0.2"
num-bigint = { version = "0.3.0", default-features = false }

[features]
default = []
Expand Down
139 changes: 139 additions & 0 deletions ff/src/fields/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use ark_serialize::{
CanonicalSerializeWithFlags, EmptyFlags, Flags,
};
use ark_std::{
cmp::min,
fmt::{Debug, Display},
hash::Hash,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
Expand Down Expand Up @@ -344,6 +345,40 @@ pub trait PrimeField:
/// Returns the underlying representation of the prime field element.
fn into_repr(&self) -> Self::BigInt;

/// Reads bytes in big-endian, and converts them to a field element.
/// If the bytes are larger than the modulus, it will reduce them.
fn from_be_bytes_mod_order(bytes: &[u8]) -> Self {
let num_modulus_bytes = ((Self::Params::MODULUS_BITS + 7) / 8) as usize;
let num_bytes_to_directly_convert = min(num_modulus_bytes - 1, bytes.len());
// Copy the leading big-endian bytes directly into a field element.
// The number of bytes directly converted must be less than the
// number of bytes needed to represent the modulus, as we must begin
// modular reduction once the data is of the same number of bytes as the modulus.
let mut bytes_to_directly_convert = Vec::new();
bytes_to_directly_convert.extend(bytes[..num_bytes_to_directly_convert].iter().rev());
// Guaranteed to not be None, as the input is less than the modulus size.
let mut res = Self::from_random_bytes(&bytes_to_directly_convert).unwrap();

// Update the result, byte by byte.
// We go through existing field arithmetic, which handles the reduction.
// TODO: If we need higher speeds, parse more bytes at once, or implement
// modular multiplication by a u64
let window_size = Self::from(256u64);
for byte in bytes[num_bytes_to_directly_convert..].iter() {
res *= window_size;
res += Self::from(*byte);
}
res
}

/// Reads bytes in little-endian, and converts them to a field element.
/// If the bytes are larger than the modulus, it will reduce them.
fn from_le_bytes_mod_order(bytes: &[u8]) -> Self {
let mut bytes_copy = bytes.to_vec();
bytes_copy.reverse();
Self::from_be_bytes_mod_order(&bytes_copy)
}

/// Return the QNR^t, for t defined by
/// `2^s * t = MODULUS - 1`, and t coprime to 2.
fn qnr_to_t() -> Self {
Expand Down Expand Up @@ -569,6 +604,7 @@ fn serial_batch_inversion_and_mul<F: Field>(v: &mut [F], coeff: &F) {
#[cfg(all(test, feature = "std"))]
mod std_tests {
use super::BitIteratorLE;

#[test]
fn bit_iterator_le() {
let bits = BitIteratorLE::new(&[0, 1 << 10]).collect::<Vec<_>>();
Expand Down Expand Up @@ -614,4 +650,107 @@ mod no_std_tests {
);
}
}

#[test]
fn test_from_be_bytes_mod_order() {
// Each test vector is a byte array,
// and its tested by parsing it with from_bytes_mod_order, and the num-bigint library.
// The bytes are currently generated from scripts/test_vectors.py.
// TODO: Eventually generate all the test vector bytes via computation with the modulus
use ark_std::string::ToString;
use num_bigint::BigUint;
use rand::Rng;

let ref_modulus =
BigUint::from_bytes_be(&<Fr as PrimeField>::Params::MODULUS.to_bytes_be());

let mut test_vectors = vec![
// 0
vec![0u8],
// 1
vec![1u8],
// 255
vec![255u8],
// 256
vec![1u8, 0u8],
// 65791
vec![1u8, 0u8, 255u8],
// 204827637402836681560342736360101429053478720705186085244545541796635082752
vec![
115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8, 8u8, 9u8,
161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8, 255u8,
255u8, 255u8, 255u8, 0u8, 0u8, 0u8,
],
// 204827637402836681560342736360101429053478720705186085244545541796635082753
vec![
115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8, 8u8, 9u8,
161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8, 255u8,
255u8, 255u8, 255u8, 0u8, 0u8, 1u8,
],
// 52435875175126190479447740508185965837690552500527637822603658699938581184512
vec![
115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8, 8u8, 9u8,
161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8, 255u8,
255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 0u8,
],
// 52435875175126190479447740508185965837690552500527637822603658699938581184513
vec![
115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8, 8u8, 9u8,
161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8, 255u8,
255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 1u8,
],
// 52435875175126190479447740508185965837690552500527637822603658699938581184514
vec![
115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8, 8u8, 9u8,
161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8, 255u8,
255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 2u8,
],
// 104871750350252380958895481016371931675381105001055275645207317399877162369026
vec![
231u8, 219u8, 78u8, 166u8, 83u8, 58u8, 250u8, 144u8, 102u8, 115u8, 176u8, 16u8,
19u8, 67u8, 176u8, 10u8, 167u8, 123u8, 72u8, 5u8, 255u8, 252u8, 183u8, 253u8,
255u8, 255u8, 255u8, 254u8, 0u8, 0u8, 0u8, 2u8,
],
// 13423584044832304762738621570095607254448781440135075282586536627184276783235328
vec![
115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8, 8u8, 9u8,
161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8, 255u8,
255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 1u8, 0u8,
],
// 115792089237316195423570985008687907853269984665640564039457584007913129639953
vec![
1u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8,
17u8,
],
// 168227964412442385903018725516873873690960537166168201862061242707851710824468
vec![
1u8, 115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8, 8u8,
9u8, 161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8, 255u8,
255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 20u8,
],
// 29695210719928072218913619902732290376274806626904512031923745164725699769008210
vec![
1u8, 0u8, 115u8, 237u8, 167u8, 83u8, 41u8, 157u8, 125u8, 72u8, 51u8, 57u8, 216u8,
8u8, 9u8, 161u8, 216u8, 5u8, 83u8, 189u8, 164u8, 2u8, 255u8, 254u8, 91u8, 254u8,
255u8, 255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 82u8,
],
];
// Add random bytestrings to the test vector list
for i in 1..512 {
let mut rng = test_rng();
let data: Vec<u8> = (0..i).map(|_| rng.gen()).collect();
test_vectors.push(data);
}
for i in test_vectors {
let mut expected_biguint = BigUint::from_bytes_be(&i);
// Reduce expected_biguint using modpow API
expected_biguint =
expected_biguint.modpow(&BigUint::from_bytes_be(&[1u8]), &ref_modulus);
let expected_string = expected_biguint.to_string();
let expected = Fr::from_str(&expected_string).unwrap();
let actual = Fr::from_be_bytes_mod_order(&i);
assert_eq!(expected, actual, "failed on test {:?}", i);
}
}
}
46 changes: 46 additions & 0 deletions scripts/test_vectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

def generate_from_bytes_mod_order_test_vector(modulus):
def gen_vector(number):
byte_arr = convert_int_to_byte_vec(number)
# s = str(number % modulus)
# return "(" + byte_arr + ", \"" + s + "\"),"
return byte_arr + ","
data = ["vec!["]

small_values_to_test = [0, 1, 255, 256, 256*256 + 255]
modulus_bits = int((len(bin(modulus)[2:]) + 7) / 8) * 8
values_to_test = small_values_to_test + [
modulus >> 8,
(modulus >> 8) + 1,
modulus - 1,
modulus,
modulus + 1,
modulus * 2,
modulus * 256,
17 + (1 << modulus_bits),
19 + (1 << modulus_bits) + modulus,
81 + (1 << modulus_bits) * 256 + modulus]

for i in values_to_test:
data += ["// " + str(i)]
data += [gen_vector(i)]

data += ["];"]
return '\n'.join(data)

def convert_int_to_byte_vec(number):
s = bin(number)[2:]
num_bytes = int((len(s) + 7) / 8)
s = s.zfill(num_bytes * 8)

byte_arr = []
for i in range(num_bytes):
byte = s[i*8: (i+1)*8]
i = int(byte, 2)
byte_arr += [str(i) + "u8"]

data = ', '.join(byte_arr)
return "vec![" + data + "]"

bls12_fr_mod = 52435875175126190479447740508185965837690552500527637822603658699938581184513
print(generate_from_bytes_mod_order_test_vector(bls12_fr_mod))

0 comments on commit 12afad6

Please sign in to comment.