Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PrimeField::from_bytes_mod_order #164

Merged
merged 17 commits into from
Jan 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
ValarDragon marked this conversation as resolved.
Show resolved Hide resolved
/// 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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also add tests for le?

// 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))