algebra
algebra copied to clipboard
Improve field code depedendent on upcoming rust features
min_const_generics: Get rid of field macros and use integer-parametrised type instead.const_trait_impl: Make all arithmetic operationsimpl<..> const TraitOp for Fpetc. This allows one to overload arithmetic operators for const evaluations of field elements, group elements, etc.specialization(perhaps with landing of chalk): This has many applications, especially in increased code sharing between sister types. One can get rid of a lot of ugly code, like instantiatinghas_glvfor different sw curves, which we rely on llvm to optimise the branches away, rather than checking for the existence of the trait impl and choosing the right impl at the type level.
Once these are done, it should be easier to implement:
- automatic impl of parameters from modulus and small set of other params. Currently, as per https://github.com/arkworks-rs/algebra/pull/96, this sort of thing is being handled using a seperate set of
const_<fn name>functions, which is rather ugly. (e.g. as perconsttrait values derivable fromconst fn, e.g.
fn main() {
const fn f(x: u64) -> u64 {
x + 5
}
trait Hello {
const X: u64;
const Y: u64 = f(Self::X);
}
impl<T> Hello for Vec<T> {
const X: u64 = 10u64;
}
println!("{}", <Vec<u32>>::Y);
}
For instance, once we have const_trait_impl, then one can use num_bits under big integer as a const_fn to determine MODULUS_BITS for Fp at compile time.
Digression:
Currently, it is already possible to do this by shifting const VALUE to the impl Fp<P>/impl AffineGroup<P>, because one can make use of const fn in the impl, rather than the trait. Given that it is a complete uncertainty when const_trait_impl will land, I consider this a viable alternative for the short term (possibly next 2+ years). However, this is extremely undesirable as such parameters cannot be accessed generically, which is the whole point of a trait.
I think the opposite strategy can be taken, so that one should instantiate concrete parameters as transparent types, like &'static [u64] rather than a type with trait bound BigInteger. Then, one can write the following:
impl $name {
..
const fn new_from_slice(&[u64]) -> Self {
..
}
}
trait FpParameters .. {
const MODULUS_SLICE: &'static [u64];
..
const MODULUS_BITS: u32 = modulus_bits(Self::MODULUS_SLICE);
const MODULUS_MINUS_ONE_DIV_TWO: Self::BigInt {
let modulus = Self::MODULUS_SLICE;
..
Self::BigInt::new_from_slice(..) // oops, we can't do this, as it isn't generic... :'(
}
}
impl FpParameters ... {
const MODULUS_SLICE: &'static [u64] = ...;
const MODULUS: Self::BigInt = Self::BigInt::new_from_slice(Self::MODULUS_SLICE);
}
And the rest of the params are handled by the generic impl. Since now all the data can be derived generically as constants from MODULUS_SLICE, which is a concrete type,
Actually, now that I think about it more, this is increasingly suggesting that one should implement the BigInteger trait for the concrete type &[u64], since most of the methods are generic over len, such as adc and sbb. Then, all the parameters can be instantiated from &'static [u64].
It's just unclear if one will get a slow down thanks to not knowing the len at compile time, whereas the methods utilised by BigInteger([u64; $limbs]), will, which could result in loop unrolling. However, the current impl does not loop unroll automatically.
Actually, this might be an area for improvement.
Alternatively, proc macros seem like a truly viable alternative to this. Since one can control everything that is evaluated without having to rely on const.