decaf377 0.10.1

A prime-order group designed for use in SNARKs over BLS12-377
Documentation
import sys

GENERATORS = {
    0x01ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001: 15,
    0x12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001: 22,
    0x4aad957a68b2955982d1347970dec005293a3afc43c8afeb95aee9ac33fd9ff: 5
}


class Properties:
    def __init__(self, p: int):
        self.p = p

    def one(self):
        return 1

    def modulus(self):
        return self.p

    def modulus_plus_1(self):
        return self.p + 1

    def modulus_minus_1(self):
        return self.p - 1

    def modulus_minus_5(self):
        return self.p - 5

    def modulus_bit_size(self):
        return self.p.bit_length()

    def modulus_plus_1_div_4(self):
        return (self.p + 1) // 4

    def two_adicity(self) -> int:
        """
        Calculate the two-adicity of this field.
        """
        acc = self.p - 1
        count = 0
        while acc & 1 == 0:
            count += 1
            acc >>= 1
        return count

    def trace(self):
        """
        (self - 1) / 2^two_adicity
        """
        return (self.p - 1) >> self.two_adicity()

    def trace_minus_one_div_two(self):
        return (self.trace() - 1) >> 1

    def mod_exp(self, a, e):
        insert = a
        acc = 1
        while e != 0:
            if e & 1 == 1:
                acc = acc * insert % self.p
            insert = (insert * insert) % self.p
            e = e >> 1
        return acc

    def legendre_symbol(self, x):
        return self.mod_exp(x, (self.p - 1) >> 1)

    def quadratic_non_residue(self):
        acc = 1
        while self.legendre_symbol(acc) == 1:
            acc += 1
        return acc

    def quadratic_non_residue_to_trace(self):
        return self.mod_exp(self.quadratic_non_residue(), self.trace())

    def modulus_minus_one_div_two(self):
        return ((self.p - 1) >> 1)

    def generator(self):
        if self.p in GENERATORS:
            return GENERATORS[self.p]
        raise ValueError(f"no known generator for {hex(self.p)}")

    def two_adic_root_of_unity(self):
        return self.mod_exp(self.generator(), self.trace())


def to_le_limbs(x, size=64, expected_limb_count=0):
    """
    Convert a number to little endian limbs, with a certain bit size.
    """
    acc = []
    while x > 0:
        acc.append(x & ((1 << size) - 1))
        x >>= size
    while len(acc) < expected_limb_count:
        acc.append(0)
    return acc


def to_monty(x, size, p):
    shift = len(to_le_limbs(p - 1, size)) * size
    return (x << shift) % p


def main(size: int, p: int, what_to_generate: str, mode):
    properties = Properties(p)
    prop = eval(what_to_generate, locals())
    expected_limb_count = (
        properties.modulus_minus_1().bit_length() + size - 1) // size
    if not isinstance(prop, list):
        prop = [prop]
    for x in prop:
        if mode == "hex":
            print(hex(x))
        elif mode == "monty":
            print(to_le_limbs(to_monty(x, size, p), size, expected_limb_count))
        elif mode == "monty_hex":
            print(hex(to_monty(x, size, p)))
        else:
            print(to_le_limbs(x, size, expected_limb_count))


if __name__ == '__main__':
    main(int(sys.argv[1]), int(sys.argv[2], 16),
         sys.argv[3], sys.argv[4] if len(sys.argv) >= 5 else None)