use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use group::ff::Field;
#[derive(Clone, Copy, Debug)]
pub enum Assigned<F> {
Zero,
Trivial(F),
Rational(F, F),
}
impl<F: Field> From<&Assigned<F>> for Assigned<F> {
fn from(val: &Assigned<F>) -> Self {
*val
}
}
impl<F: Field> From<&F> for Assigned<F> {
fn from(numerator: &F) -> Self {
Assigned::Trivial(*numerator)
}
}
impl<F: Field> From<F> for Assigned<F> {
fn from(numerator: F) -> Self {
Assigned::Trivial(numerator)
}
}
impl<F: Field> From<(F, F)> for Assigned<F> {
fn from((numerator, denominator): (F, F)) -> Self {
Assigned::Rational(numerator, denominator)
}
}
impl<F: Field> PartialEq for Assigned<F> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Zero, Self::Zero) => true,
(Self::Zero, x) | (x, Self::Zero) => x.is_zero_vartime(),
(Self::Rational(_, denominator), x) | (x, Self::Rational(_, denominator))
if denominator.is_zero_vartime() =>
{
x.is_zero_vartime()
}
(Self::Trivial(lhs), Self::Trivial(rhs)) => lhs == rhs,
(Self::Trivial(x), Self::Rational(numerator, denominator))
| (Self::Rational(numerator, denominator), Self::Trivial(x)) => {
&(*x * denominator) == numerator
}
(
Self::Rational(lhs_numerator, lhs_denominator),
Self::Rational(rhs_numerator, rhs_denominator),
) => *lhs_numerator * rhs_denominator == *lhs_denominator * rhs_numerator,
}
}
}
impl<F: Field> Eq for Assigned<F> {}
impl<F: Field> Neg for Assigned<F> {
type Output = Assigned<F>;
fn neg(self) -> Self::Output {
match self {
Self::Zero => Self::Zero,
Self::Trivial(numerator) => Self::Trivial(-numerator),
Self::Rational(numerator, denominator) => Self::Rational(-numerator, denominator),
}
}
}
impl<F: Field> Neg for &Assigned<F> {
type Output = Assigned<F>;
fn neg(self) -> Self::Output {
-*self
}
}
impl<F: Field> Add for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: Assigned<F>) -> Assigned<F> {
match (self, rhs) {
(Self::Zero, _) => rhs,
(_, Self::Zero) => self,
(Self::Rational(_, denominator), other) | (other, Self::Rational(_, denominator))
if denominator.is_zero_vartime() =>
{
other
}
(Self::Trivial(lhs), Self::Trivial(rhs)) => Self::Trivial(lhs + rhs),
(Self::Rational(numerator, denominator), Self::Trivial(other))
| (Self::Trivial(other), Self::Rational(numerator, denominator)) => {
Self::Rational(numerator + denominator * other, denominator)
}
(
Self::Rational(lhs_numerator, lhs_denominator),
Self::Rational(rhs_numerator, rhs_denominator),
) => Self::Rational(
lhs_numerator * rhs_denominator + lhs_denominator * rhs_numerator,
lhs_denominator * rhs_denominator,
),
}
}
}
impl<F: Field> Add<F> for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: F) -> Assigned<F> {
self + Self::Trivial(rhs)
}
}
impl<F: Field> Add<F> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: F) -> Assigned<F> {
*self + rhs
}
}
impl<F: Field> Add<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: &Self) -> Assigned<F> {
self + *rhs
}
}
impl<F: Field> Add<Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: Assigned<F>) -> Assigned<F> {
*self + rhs
}
}
impl<F: Field> Add<&Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn add(self, rhs: &Assigned<F>) -> Assigned<F> {
*self + *rhs
}
}
impl<F: Field> AddAssign for Assigned<F> {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl<F: Field> AddAssign<&Assigned<F>> for Assigned<F> {
fn add_assign(&mut self, rhs: &Self) {
*self = *self + rhs;
}
}
impl<F: Field> Sub for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: Assigned<F>) -> Assigned<F> {
self + (-rhs)
}
}
impl<F: Field> Sub<F> for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: F) -> Assigned<F> {
self + (-rhs)
}
}
impl<F: Field> Sub<F> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: F) -> Assigned<F> {
*self - rhs
}
}
impl<F: Field> Sub<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: &Self) -> Assigned<F> {
self - *rhs
}
}
impl<F: Field> Sub<Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: Assigned<F>) -> Assigned<F> {
*self - rhs
}
}
impl<F: Field> Sub<&Assigned<F>> for &Assigned<F> {
type Output = Assigned<F>;
fn sub(self, rhs: &Assigned<F>) -> Assigned<F> {
*self - *rhs
}
}
impl<F: Field> SubAssign for Assigned<F> {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl<F: Field> SubAssign<&Assigned<F>> for Assigned<F> {
fn sub_assign(&mut self, rhs: &Self) {
*self = *self - rhs;
}
}
impl<F: Field> Mul for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: Assigned<F>) -> Assigned<F> {
match (self, rhs) {
(Self::Zero, _) | (_, Self::Zero) => Self::Zero,
(Self::Trivial(lhs), Self::Trivial(rhs)) => Self::Trivial(lhs * rhs),
(Self::Rational(numerator, denominator), Self::Trivial(other))
| (Self::Trivial(other), Self::Rational(numerator, denominator)) => {
Self::Rational(numerator * other, denominator)
}
(
Self::Rational(lhs_numerator, lhs_denominator),
Self::Rational(rhs_numerator, rhs_denominator),
) => Self::Rational(
lhs_numerator * rhs_numerator,
lhs_denominator * rhs_denominator,
),
}
}
}
impl<F: Field> Mul<F> for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: F) -> Assigned<F> {
self * Self::Trivial(rhs)
}
}
impl<F: Field> Mul<F> for &Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: F) -> Assigned<F> {
*self * rhs
}
}
impl<F: Field> Mul<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: &Assigned<F>) -> Assigned<F> {
self * *rhs
}
}
impl<F: Field> MulAssign for Assigned<F> {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl<F: Field> MulAssign<&Assigned<F>> for Assigned<F> {
fn mul_assign(&mut self, rhs: &Self) {
*self = *self * rhs;
}
}
impl<F: Field> Assigned<F> {
pub fn numerator(&self) -> F {
match self {
Self::Zero => F::ZERO,
Self::Trivial(x) => *x,
Self::Rational(numerator, _) => *numerator,
}
}
pub fn denominator(&self) -> Option<F> {
match self {
Self::Zero => None,
Self::Trivial(_) => None,
Self::Rational(_, denominator) => Some(*denominator),
}
}
pub fn is_zero_vartime(&self) -> bool {
match self {
Self::Zero => true,
Self::Trivial(x) => x.is_zero_vartime(),
Self::Rational(numerator, denominator) => {
numerator.is_zero_vartime() || denominator.is_zero_vartime()
}
}
}
#[must_use]
pub fn double(&self) -> Self {
match self {
Self::Zero => Self::Zero,
Self::Trivial(x) => Self::Trivial(x.double()),
Self::Rational(numerator, denominator) => {
Self::Rational(numerator.double(), *denominator)
}
}
}
#[must_use]
pub fn square(&self) -> Self {
match self {
Self::Zero => Self::Zero,
Self::Trivial(x) => Self::Trivial(x.square()),
Self::Rational(numerator, denominator) => {
Self::Rational(numerator.square(), denominator.square())
}
}
}
#[must_use]
pub fn cube(&self) -> Self {
self.square() * self
}
pub fn invert(&self) -> Self {
match self {
Self::Zero => Self::Zero,
Self::Trivial(x) => Self::Rational(F::ONE, *x),
Self::Rational(numerator, denominator) => Self::Rational(*denominator, *numerator),
}
}
pub fn evaluate(self) -> F {
match self {
Self::Zero => F::ZERO,
Self::Trivial(x) => x,
Self::Rational(numerator, denominator) => {
if denominator == F::ONE {
numerator
} else {
numerator * denominator.invert().unwrap_or(F::ZERO)
}
}
}
}
}
#[cfg(test)]
mod tests {
use halo2curves::pasta::Fp;
use super::Assigned;
#[test]
fn add_trivial_to_inv0_rational() {
let a = Assigned::Trivial(Fp::from(2));
let b = Assigned::Rational(Fp::one(), Fp::zero());
assert_eq!((a + b).evaluate(), a.evaluate());
assert_eq!((b + a).evaluate(), a.evaluate());
}
#[test]
fn add_rational_to_inv0_rational() {
let a = Assigned::Rational(Fp::one(), Fp::from(2));
let b = Assigned::Rational(Fp::one(), Fp::zero());
assert_eq!((a + b).evaluate(), a.evaluate());
assert_eq!((b + a).evaluate(), a.evaluate());
}
#[test]
fn sub_trivial_from_inv0_rational() {
let a = Assigned::Trivial(Fp::from(2));
let b = Assigned::Rational(Fp::one(), Fp::zero());
assert_eq!((b - a).evaluate(), (-a).evaluate());
assert_eq!((a - b).evaluate(), a.evaluate());
}
#[test]
fn sub_rational_from_inv0_rational() {
let a = Assigned::Rational(Fp::one(), Fp::from(2));
let b = Assigned::Rational(Fp::one(), Fp::zero());
assert_eq!((b - a).evaluate(), (-a).evaluate());
assert_eq!((a - b).evaluate(), a.evaluate());
}
#[test]
fn mul_rational_by_inv0_rational() {
let a = Assigned::Rational(Fp::one(), Fp::from(2));
let b = Assigned::Rational(Fp::one(), Fp::zero());
assert_eq!((a * b).evaluate(), Fp::zero());
assert_eq!((b * a).evaluate(), Fp::zero());
}
}
#[cfg(test)]
mod proptests {
use std::{
cmp,
ops::{Add, Mul, Neg, Sub},
};
use group::ff::Field;
use halo2curves::pasta::Fp;
use proptest::{collection::vec, prelude::*, sample::select};
use super::Assigned;
trait UnaryOperand: Neg<Output = Self> {
fn double(&self) -> Self;
fn square(&self) -> Self;
fn cube(&self) -> Self;
fn inv0(&self) -> Self;
}
impl<F: Field> UnaryOperand for F {
fn double(&self) -> Self {
self.double()
}
fn square(&self) -> Self {
self.square()
}
fn cube(&self) -> Self {
self.cube()
}
fn inv0(&self) -> Self {
self.invert().unwrap_or(F::ZERO)
}
}
impl<F: Field> UnaryOperand for Assigned<F> {
fn double(&self) -> Self {
self.double()
}
fn square(&self) -> Self {
self.square()
}
fn cube(&self) -> Self {
self.cube()
}
fn inv0(&self) -> Self {
self.invert()
}
}
#[derive(Clone, Debug)]
enum UnaryOperator {
Neg,
Double,
Square,
Cube,
Inv0,
}
const UNARY_OPERATORS: &[UnaryOperator] = &[
UnaryOperator::Neg,
UnaryOperator::Double,
UnaryOperator::Square,
UnaryOperator::Cube,
UnaryOperator::Inv0,
];
impl UnaryOperator {
fn apply<F: UnaryOperand>(&self, a: F) -> F {
match self {
Self::Neg => -a,
Self::Double => a.double(),
Self::Square => a.square(),
Self::Cube => a.cube(),
Self::Inv0 => a.inv0(),
}
}
}
trait BinaryOperand: Sized + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self> {}
impl<F: Field> BinaryOperand for F {}
impl<F: Field> BinaryOperand for Assigned<F> {}
#[derive(Clone, Debug)]
enum BinaryOperator {
Add,
Sub,
Mul,
}
const BINARY_OPERATORS: &[BinaryOperator] = &[
BinaryOperator::Add,
BinaryOperator::Sub,
BinaryOperator::Mul,
];
impl BinaryOperator {
fn apply<F: BinaryOperand>(&self, a: F, b: F) -> F {
match self {
Self::Add => a + b,
Self::Sub => a - b,
Self::Mul => a * b,
}
}
}
#[derive(Clone, Debug)]
enum Operator {
Unary(UnaryOperator),
Binary(BinaryOperator),
}
prop_compose! {
fn arb_element()(val in any::<u64>()) -> Fp {
Fp::from(val)
}
}
prop_compose! {
fn arb_trivial()(element in arb_element()) -> Assigned<Fp> {
Assigned::Trivial(element)
}
}
prop_compose! {
fn arb_rational()(
numerator in arb_element(),
denominator in prop_oneof![
1 => Just(Fp::zero()),
2 => arb_element(),
],
) -> Assigned<Fp> {
Assigned::Rational(numerator, denominator)
}
}
prop_compose! {
fn arb_operators(num_unary: usize, num_binary: usize)(
unary in vec(select(UNARY_OPERATORS), num_unary),
binary in vec(select(BINARY_OPERATORS), num_binary),
) -> Vec<Operator> {
unary.into_iter()
.map(Operator::Unary)
.chain(binary.into_iter().map(Operator::Binary))
.collect()
}
}
prop_compose! {
fn arb_testcase()(
num_unary in 0usize..5,
num_binary in 0usize..5,
)(
values in vec(
prop_oneof![
1 => Just(Assigned::Zero),
2 => arb_trivial(),
2 => arb_rational(),
],
cmp::max(usize::from(num_unary > 0), num_binary + 1)),
operations in arb_operators(num_unary, num_binary).prop_shuffle(),
) -> (Vec<Assigned<Fp>>, Vec<Operator>) {
(values, operations)
}
}
proptest! {
#[test]
fn operation_commutativity((values, operations) in arb_testcase()) {
let elements: Vec<_> = values.iter().cloned().map(|v| v.evaluate()).collect();
fn evaluate<F: UnaryOperand + BinaryOperand>(
items: Vec<F>,
operators: &[Operator],
) -> F {
let mut ops = operators.iter();
let mut res = items.into_iter().reduce(|mut a, b| loop {
match ops.next() {
Some(Operator::Unary(op)) => a = op.apply(a),
Some(Operator::Binary(op)) => break op.apply(a, b),
None => unreachable!(),
}
}).unwrap();
loop {
match ops.next() {
Some(Operator::Unary(op)) => res = op.apply(res),
Some(Operator::Binary(_)) => unreachable!(),
None => break res,
}
}
}
let deferred_result = evaluate(values, &operations);
let evaluated_result = evaluate(elements, &operations);
assert_eq!(deferred_result.evaluate(), evaluated_result);
}
}
}