# Copyright 2015 Brian Smith.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND BRIAN SMITH AND THE AUTHORS DISCLAIM
# ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL BRIAN SMITH OR THE AUTHORS
# BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# Usage: python lib/gfp_generate.py --outdir
modp_template = """
{
{ %(limbs)s
},
%(bits)d / 8 / sizeof(Limb), /* count */
%(bits)d / 8, /* bytes */
%(bits)d, /* bits */
gfp_%(curve_name)s_mul_mont_%(name)s,
%(k)s, /* k */
/* rr */
{ %(rr)s
},
}
"""
nistz_declarations_template = """
extern void ecp_nistz%(bits)d_add(Limb *r, const Limb *a, const Limb *b);
extern void ecp_nistz%(bits)d_mul_mont(Limb *r, const Limb *a, const Limb *b);
extern void ecp_nistz%(bits)d_neg(Limb *r, const Limb *s);
extern void ecp_nistz%(bits)d_sqr_mont(Limb *r, const Limb *s);
extern void ecp_nistz%(bits)d_sub(Limb *r, const Limb *a, const Limb *b);
extern void ecp_nistz%(bits)d_point_double(JacobianPoint *r,
const JacobianPoint *s);
extern void ecp_nistz%(bits)d_point_add(JacobianPoint *r,
const JacobianPoint *a,
const JacobianPoint *b);
extern void ecp_nistz%(bits)d_point_add_affine(JacobianPoint *r,
const JacobianPoint *a,
const XY00Point *b);
#define gfp_%(name)s_add_mod_q ecp_nistz%(bits)d_add
#define gfp_%(name)s_mul_mont_q ecp_nistz%(bits)d_mul_mont
#define gfp_%(name)s_neg_mod_q ecp_nistz%(bits)d_neg
#define gfp_%(name)s_sqr_mont_q ecp_nistz%(bits)d_sqr_mont
#define gfp_%(name)s_sub_mod_q ecp_nistz%(bits)d_sub
#define gfp_%(name)s_point_double ecp_nistz%(bits)d_point_double
#define gfp_%(name)s_point_add_jacobian ecp_nistz%(bits)d_point_add
#define gfp_%(name)s_point_add_xy00 ecp_nistz%(bits)d_point_add_affine
static void gfp_%(name)s_mul_mont_n(Limb *r, const Limb *a, const Limb *b);
"""
nistz_definitions_template = """
static void gfp_%(name)s_mul_mont_n(Limb *r, const Limb *a, const Limb *b) {
GFp_limbarray_mul_mont(r, gfp_%(name)s.n.count, a, b, gfp_%(name)s.n.count,
&gfp_%(name)s.n);
}
"""
curve_template = """
/* Copyright 2015 Brian Smith.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
/* This entire file was generated by gfp_generate_curve.py. */
#include "gfp_internal.h"
%(declarations)s
static const GFp_Curve gfp_%(name)s = {
/* q */
%(q)s,
/* n */
%(n)s,
/* one (Montgomery) */
{ { %(one)s
} },
/* three (Montgomery) */
{ { %(three)s
} },
/* b (Montgomery) */
{ { %(b)s
} },
/* G (Montgomery) */
{ { { %(Gx)s
} },
{ { %(Gy)s
} }
},
/* q_minus_n */
{ %(q_minus_n)s
},
gfp_%(name)s_add_mod_q,
gfp_%(name)s_neg_mod_q,
gfp_%(name)s_sqr_mont_q,
gfp_%(name)s_sub_mod_q,
gfp_%(name)s_point_double,
gfp_%(name)s_point_add_jacobian,
gfp_%(name)s_point_add_xy00,
};
const GFp_Curve *GFp_%(name)s(void) {
return &gfp_%(name)s;
}
%(definitions)s
"""
import math
import random
import sys
def fill_in_template(template, value_map, indent):
# In order to enhance readability, the definitions of the templates put the
# '"""' delimiters on separate lines from the first/last lines of the
# template, but that results in unwanted blank lines at the beginning and
# end. |splitlines| removes the last blank line but not the first one.
lines = template.splitlines()
if lines[0] == "":
del lines[0]
unindented = "\n".join(lines) % value_map
# Indent all non-blank lines with |indent| spaces, except for the first
# line. The first line isn't indented because the embedding template
# provides its indention.
unindented_lines = unindented.splitlines()
indented_lines = unindented_lines[0:1] + \
[(' ' * indent) + l if l != "" else l
for l in unindented_lines[1:]]
return '\n'.join(indented_lines)
def to_montgomery(x, p):
return (x * 2**p.bit_length()) % p
# Given a number |x|, return a generator of a sequence |a| such that
#
# x == a[0] + a[1]*2**limb_bits + ...
#
def little_endian_limbs(x, limb_bits):
if x < 0:
raise ValueError("x must be positive");
if x == 0:
yield [0]
return
while x != 0:
x, digit = divmod(x, 2**limb_bits)
yield digit
def format_hex_or_decimal(x, limb_bits):
"""format_hex_or_decimal formats a positive integer in decimal if its
magnitude, when interpreted as a two's complement signed integer,
is small, and formats it in fixed-width hexadecimal otherwise. This
inconsistent formatting makes the special structure of the large
values used in high-performance curves easier to see."""
if x < 0:
raise ValueError("x must be positive")
if x <= 0x100:
return "%d" % x
if x >= 2**limb_bits - 0x100:
return "(Limb)(%d)" % (x - (2**limb_bits)) # two's complement to negative
return ("0x%0" + ("%d" % (limb_bits / 4)) + "x") % x
def group_list(x, group_len):
"""pair_list returns a list with the items in x grouped into sub-arrays,
|group_len| at a time. For example, |group_list([1,2,3,4,5,6], 2)|
returns |[[1, 2], [3, 4], [5, 6]]|."""
return [x[i:i+group_len] for i in range(0, len(x), group_len)]
def format_big_int(x, limbs, limb_bits, indent):
parts = list(little_endian_limbs(x, limb_bits))
parts += (limbs - len(parts)) * [0]
grouped_parts = group_list(parts, 128 // limb_bits)
formatted_parts = [", ".join([format_hex_or_decimal(x, limb_bits)
for x in group])
for group in grouped_parts]
first = formatted_parts[0]
rest = [' ' * (indent + 2) + x for x in formatted_parts[1:]]
return ",\n".join([first] + rest)
# http://rosettacode.org/wiki/Modular_inverse#Python
def modinv(a, m):
def extended_gcd(aa, bb):
last_rem, rem = abs(aa), abs(bb)
x, last_x, y, last_y = 0, 1, 1, 0
while rem:
last_rem, (quotient, rem) = rem, divmod(last_rem, rem)
x, last_x = last_x - quotient * x, x
y, last_y = last_y - quotient * y, y
return (last_rem,
last_x * (-1 if aa < 0 else 1),
last_y * (-1 if bb < 0 else 1))
g, x, y = extended_gcd(a, m)
if g != 1:
raise ValueError
return x % m
def format_curve_name(g):
return "secp%dr1" % g["q"].bit_length()
def format_modp(g, name, limb_count, limb_bits, indent):
p = g[name]
values = {
"bits" : p.bit_length(),
"limbs" : format_big_int(p, limb_count, limb_bits, 2),
"name" : name,
"curve_name" : format_curve_name(g),
"k": format_hex_or_decimal(modinv(-p, 2**limb_bits), limb_bits),
"rr": format_big_int(to_montgomery(2**p.bit_length(), p), limb_count,
limb_bits, 2)
}
return fill_in_template(modp_template, values, indent)
def format_prime_curve(g, arch):
if g["a"] != -3:
raise ValueError("Only curves where a == -3 are supported.")
if g["cofactor"] != 1:
raise ValueError("Only curves with cofactor 1 are supported.")
if g["q"] != g["spec_q"]:
raise ValueError("Polynomial representation of q doesn't match the "
"literal version given in the specification.")
if g["n"] != g["spec_n"]:
raise ValueError("Polynomial representation of n doesn't match the "
"literal version given in the specification.")
limb_count = (g["q"].bit_length() + arch["bits"] - 1) // arch["bits"]
limb_bits = arch["bits"]
values = {
"bits": g["q"].bit_length(),
"name": format_curve_name(g),
"q" : format_modp(g, "q", limb_count, limb_bits, 2),
"n" : format_modp(g, "n", limb_count, limb_bits, 2),
"one" : format_big_int(to_montgomery(1, g["q"]), limb_count, limb_bits,
4),
"three" : format_big_int(to_montgomery(3, g["q"]), limb_count,
limb_bits, 4),
"b" : format_big_int(to_montgomery(g["b"], g["q"]), limb_count,
limb_bits, 4),
"Gx" : format_big_int(to_montgomery(g["Gx"], g["q"]), limb_count,
limb_bits, 6),
"Gy" : format_big_int(to_montgomery(g["Gy"], g["q"]), limb_count,
limb_bits, 6),
"q_minus_n" : format_big_int(g["q"] - g["n"], limb_count, limb_bits,
2),
}
declarations_template = nistz_declarations_template
definitions_template = nistz_definitions_template
values["declarations"] = fill_in_template(declarations_template, values, 0)
values["definitions"] = fill_in_template(definitions_template, values, 0)
return fill_in_template(curve_template, values, 0) + "\n"
# The curve parameters are from
# http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf. |q| and |n| are
# given in the polynomial representation so that their special structures are
# more evident. |spec_q| and |spec_n| are the literal representations of the
# same values given in the specs, and are provided only so that
# |generate_prime_curve_code| can verify that |q| and |n| are correct.
secp256r1 = {
"q": 2**256 - 2**224 + 2**192 + 2**96 - 1,
"spec_q" : 115792089210356248762697446949407573530086143415290314195533631308867097853951,
"n": 2**256 - 2**224 + 2**192 - 2**128 + 0xbce6faada7179e84f3b9cac2fc632551,
"spec_n" : 115792089210356248762697446949407573529996955224135760342422259061068512044369,
"a": -3,
"b": 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b,
"Gx": 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
"Gy": 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
"cofactor": 1,
}
x86 = {
"name" : "x86",
"bits" : 32,
}
x64 = {
"name" : "x64",
"bits" : 64,
}
armv4 = {
"name" : "armv4",
"bits" : 32,
}
armv8 = {
"name" : "armv8",
"bits" : 64,
}
architectures = {
x86["name"] : x86,
x64["name"] : x64,
armv4["name"] : armv4,
armv8["name"] : armv8,
}
import argparse
import os
def generate_prime_curve_file(g, arch, out_dir):
code = format_prime_curve(g, arch)
out_path = os.path.join(out_dir, "gfp_%s.c" % format_curve_name(g))
with open(out_path, "wb") as f:
f.write(code)
arg_parser = argparse.ArgumentParser(description='Generate GFp code.')
arg_parser.add_argument("--outdir", help="The output directory", required=True)
arg_parser.add_argument("--arch", help="The architecture to generate code for",
choices = architectures.keys(), required=True)
args = arg_parser.parse_args()
arch = architectures[args.arch]
generate_prime_curve_file(secp256r1, arch, args.outdir)