AbstractFqp.java
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.crypto.altbn128;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;
import com.google.common.base.MoreObjects;
/**
* Adapted from the pc_ecc (Apache 2 License) implementation:
* https://github.com/ethereum/py_ecc/blob/master/py_ecc/bn128/bn128_field_elements.py
*
* @param <T> the type parameter
*/
@SuppressWarnings("rawtypes")
public abstract class AbstractFqp<T extends AbstractFqp> implements FieldElement<T> {
private static final BigInteger BIGINT_2 = BigInteger.valueOf(2);
/** The Degree. */
protected final int degree;
/** The Modulus coefficients. */
protected final Fq[] modulusCoefficients;
/** The Coefficients. */
protected final Fq[] coefficients;
/**
* Instantiates a new Abstract fqp.
*
* @param degree the degree
* @param modulusCoefficients the modulus coefficients
* @param coefficients the coefficients
*/
protected AbstractFqp(final int degree, final Fq[] modulusCoefficients, final Fq[] coefficients) {
if (degree != coefficients.length) {
throw new IllegalArgumentException(
String.format("point is %d degree but got %d coefficients", degree, coefficients.length));
}
if (degree != modulusCoefficients.length) {
throw new IllegalArgumentException(
String.format(
"point is %d degree but got %d modulus coefficients", degree, coefficients.length));
}
this.degree = degree;
this.modulusCoefficients = modulusCoefficients;
this.coefficients = coefficients;
}
/**
* New instance t.
*
* @param coefficients the coefficients
* @return the t
*/
protected abstract T newInstance(final Fq[] coefficients);
/**
* Get coefficients fq [ ].
*
* @return the fq [ ]
*/
public Fq[] getCoefficients() {
return coefficients;
}
@Override
public boolean isValid() {
for (final Fq fq : coefficients) {
if (!fq.isValid()) {
return false;
}
}
return true;
}
@Override
public boolean isZero() {
for (final Fq fq : coefficients) {
if (!fq.isZero()) {
return false;
}
}
return true;
}
@Override
public T add(final T other) {
final Fq[] result = new Fq[coefficients.length];
for (int i = 0; i < coefficients.length; ++i) {
result[i] = coefficients[i].add(other.coefficients[i]);
}
return newInstance(result);
}
@Override
public T subtract(final T other) {
final Fq[] result = new Fq[coefficients.length];
for (int i = 0; i < coefficients.length; ++i) {
result[i] = coefficients[i].subtract(other.coefficients[i]);
}
return newInstance(result);
}
@Override
public T multiply(final int n) {
final Fq[] result = new Fq[degree];
for (int i = 0; i < degree; ++i) {
result[i] = coefficients[i].multiply(n);
}
return newInstance(result);
}
@Override
public T multiply(final T other) {
final Fq[] b = new Fq[degree * 2 - 1];
Arrays.fill(b, Fq.zero());
for (int i = 0; i < degree; ++i) {
for (int j = 0; j < degree; ++j) {
b[i + j] = b[i + j].add(coefficients[i].multiply(other.coefficients[j]));
}
}
for (int i = b.length; i > degree; --i) {
final Fq top = b[i - 1];
final int exp = i - degree - 1;
for (int j = 0; j < degree; ++j) {
b[exp + j] = b[exp + j].subtract(top.multiply(modulusCoefficients[j]));
}
}
return newInstance(Arrays.copyOfRange(b, 0, degree));
}
@Override
public T divide(final T other) {
final T inverse = newInstance(other.inverse());
return multiply(inverse);
}
@Override
public T negate() {
final Fq[] negated = Arrays.stream(coefficients).map(Fq::negate).toArray(Fq[]::new);
return newInstance(negated);
}
private T one() {
final Fq[] result = new Fq[degree];
Arrays.fill(result, Fq.zero());
result[0] = Fq.one();
return newInstance(result);
}
@SuppressWarnings("unchecked")
@Override
public T power(final int n) {
if (n == 0) {
return one();
} else if (n == 1) {
return newInstance(coefficients);
} else if (n % 2 == 0) {
return (T) multiply((T) this).power(n / 2);
} else {
return (T) multiply((T) this).power(n / 2).multiply(this);
}
}
@SuppressWarnings("unchecked")
@Override
public T power(final BigInteger n) {
if (n.compareTo(BigInteger.ZERO) == 0) {
return one();
}
if (n.compareTo(BigInteger.ONE) == 0) {
return (T) this;
} else if (n.mod(BIGINT_2).compareTo(BigInteger.ZERO) == 0) {
return (T) multiply((T) this).power(n.divide(BIGINT_2));
} else {
return (T) multiply((T) this).power(n.divide(BIGINT_2)).multiply(this);
}
}
/**
* Inverse fq [ ].
*
* @return the fq [ ]
*/
protected Fq[] inverse() {
Fq[] lm = lm();
Fq[] hm = hm();
Fq[] low = low();
Fq[] high = high();
while (deg(low) > 0) {
final Fq[] r = polyRoundedDiv(high, low);
final Fq[] nm = Arrays.copyOf(hm, hm.length);
final Fq[] neww = Arrays.copyOf(high, high.length);
for (int i = 0; i < degree + 1; ++i) {
for (int j = 0; j < degree + 1 - i; ++j) {
nm[i + j] = nm[i + j].subtract(lm[i].multiply(r[j]));
neww[i + j] = neww[i + j].subtract(low[i].multiply(r[j]));
}
}
high = low;
hm = lm;
low = neww;
lm = nm;
}
for (int i = 0; i < lm.length; ++i) {
lm[i] = lm[i].divide(low[0]);
}
return Arrays.copyOfRange(lm, 0, degree);
}
private static Fq[] polyRoundedDiv(final Fq[] a, final Fq[] b) {
final int degA = deg(a);
final int degB = deg(b);
final Fq[] temp = Arrays.copyOf(a, a.length);
final Fq[] o = new Fq[a.length];
Arrays.fill(o, Fq.zero());
for (int i = degA - degB; i >= 0; --i) {
o[i] = o[i].add(temp[degB + i].divide(b[degB]));
for (int j = 0; j <= degB; ++j) {
temp[i + j] = temp[i + j].subtract(o[j]);
}
}
return o;
}
private static int deg(final Fq[] p) {
int d = p.length - 1;
while (d >= 0 && p[d].equals(Fq.zero())) {
--d;
}
return d;
}
private Fq[] lm() {
final Fq[] lm = new Fq[degree + 1];
Arrays.fill(lm, Fq.zero());
lm[0] = Fq.one();
return lm;
}
private Fq[] hm() {
final Fq[] hm = new Fq[degree + 1];
Arrays.fill(hm, Fq.zero());
return hm;
}
private Fq[] low() {
final Fq[] low = Arrays.copyOfRange(coefficients, 0, coefficients.length + 1);
low[low.length - 1] = Fq.zero();
return low;
}
private Fq[] high() {
final Fq[] high = Arrays.copyOfRange(modulusCoefficients, 0, modulusCoefficients.length + 1);
high[high.length - 1] = Fq.one();
return high;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AbstractFqp)) {
return false;
}
final AbstractFqp<?> other = (AbstractFqp<?>) obj;
if (degree != other.degree) return false;
if (!Arrays.equals(modulusCoefficients, other.modulusCoefficients)) return false;
return Arrays.equals(coefficients, other.coefficients);
}
@Override
public int hashCode() {
return Objects.hash(
degree, Arrays.hashCode(modulusCoefficients), Arrays.hashCode(coefficients));
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass()).add("coefficients", coefficients).toString();
}
}