EC: Add C code generation to mk/generate_curves.py.

Generate some of the C boilerplate, particularly the large constants.
The output is written into target/curves/, and can be merged into
the actual code in crypto/fipsmodule/ec/ using a two-way merge tool;
this is the same as the Rust code generation.

Changes to gfp_p{256,384}.c are due to differences in the generator's
output:

* The generator doesn't generate trailing commas in arrays.
* The generator consistently avoids adding leading zeros to hex
  constants, and consistently format values less than 10 in decimal;
  the exiting code used a mix of styles.
* The generator wraps arrays consistently; the existing code used a
  mix of wrapping styles.
* The generator does not nest constants in the functions that need
  them. This was changed to support future refactorings.
This commit is contained in:
Brian Smith 2023-10-27 10:34:02 -07:00
parent 75cbe475ff
commit d87972edc9
3 changed files with 174 additions and 52 deletions

View File

@ -23,17 +23,19 @@ typedef Limb Scalar[P256_LIMBS];
#include "../bn/internal.h"
static const BN_ULONG N[P256_LIMBS] = {
TOBN(0xf3b9cac2, 0xfc632551),
TOBN(0xbce6faad, 0xa7179e84),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0)
};
static const BN_ULONG N_N0[] = {
BN_MONT_CTX_N0(0xccd1c8aa, 0xee00bc4f)
};
void p256_scalar_mul_mont(ScalarMont r, const ScalarMont a,
const ScalarMont b) {
static const BN_ULONG N[] = {
TOBN(0xf3b9cac2, 0xfc632551),
TOBN(0xbce6faad, 0xa7179e84),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0x00000000),
};
static const BN_ULONG N_N0[] = {
BN_MONT_CTX_N0(0xccd1c8aa, 0xee00bc4f)
};
/* XXX: Inefficient. TODO: optimize with dedicated multiplication routine. */
bn_mul_mont(r, a, b, N, N_N0, P256_LIMBS);
}

View File

@ -29,14 +29,13 @@ typedef Limb Elem[P384_LIMBS];
typedef Limb ScalarMont[P384_LIMBS];
typedef Limb Scalar[P384_LIMBS];
static const BN_ULONG Q[P384_LIMBS] = {
TOBN(0x00000000, 0xffffffff),
TOBN(0xffffffff, 0x00000000),
TOBN(0, 0xffffffff),
TOBN(0xffffffff, 0),
TOBN(0xffffffff, 0xfffffffe),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff)
};
static const BN_ULONG N[P384_LIMBS] = {
@ -45,15 +44,34 @@ static const BN_ULONG N[P384_LIMBS] = {
TOBN(0xc7634d81, 0xf4372ddf),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff)
};
static const BN_ULONG ONE[P384_LIMBS] = {
TOBN(0xffffffff, 1), TOBN(0, 0xffffffff), TOBN(0, 1), TOBN(0, 0), TOBN(0, 0),
TOBN(0xffffffff, 1),
TOBN(0, 0xffffffff),
TOBN(0, 1),
TOBN(0, 0),
TOBN(0, 0),
TOBN(0, 0)
};
static const Elem Q_PLUS_1_SHR_1 = {
TOBN(0, 0x80000000),
TOBN(0x7fffffff, 0x80000000),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff),
TOBN(0x7fffffff, 0xffffffff)
};
static const BN_ULONG Q_N0[] = {
BN_MONT_CTX_N0(1, 1)
};
static const BN_ULONG N_N0[] = {
BN_MONT_CTX_N0(0x6ed46089, 0xe88fdc45)
};
/* XXX: MSVC for x86 warns when it fails to inline these functions it should
* probably inline. */
@ -141,12 +159,6 @@ static void elem_div_by_2(Elem r, const Elem a) {
carry = new_carry;
}
static const Elem Q_PLUS_1_SHR_1 = {
TOBN(0x00000000, 0x80000000), TOBN(0x7fffffff, 0x80000000),
TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff),
TOBN(0xffffffff, 0xffffffff), TOBN(0x7fffffff, 0xffffffff),
};
Elem adjusted;
BN_ULONG carry2 = limbs_add(adjusted, r, Q_PLUS_1_SHR_1, P384_LIMBS);
dev_assert_secret(carry2 == 0);
@ -155,9 +167,6 @@ static void elem_div_by_2(Elem r, const Elem a) {
}
static inline void elem_mul_mont(Elem r, const Elem a, const Elem b) {
static const BN_ULONG Q_N0[] = {
BN_MONT_CTX_N0(0x1, 0x1)
};
/* XXX: Not (clearly) constant-time; inefficient.*/
bn_mul_mont(r, a, b, Q, Q_N0, P384_LIMBS);
}
@ -203,9 +212,6 @@ void p384_elem_neg(Elem r, const Elem a) {
void p384_scalar_mul_mont(ScalarMont r, const ScalarMont a,
const ScalarMont b) {
static const BN_ULONG N_N0[] = {
BN_MONT_CTX_N0(0x6ed46089, 0xe88fdc45)
};
/* XXX: Inefficient. TODO: Add dedicated multiplication routine. */
bn_mul_mont(r, a, b, N, N_N0, P384_LIMBS);
}

View File

@ -16,7 +16,7 @@
from textwrap import wrap
curve_template = """
rs_template = """
// Copyright 2016-2023 Brian Smith.
//
// Permission to use, copy, modify, and/or distribute this software for any
@ -243,8 +243,11 @@ import sys
def whole_bit_length(p, limb_bits):
return (p.bit_length() + limb_bits - 1) // limb_bits * limb_bits
def to_montgomery_value(x, p, limb_bits):
return (x * 2**whole_bit_length(p, limb_bits)) % p
def to_montgomery_(x, p, limb_bits):
value = (x * 2**whole_bit_length(p, limb_bits)) % p
value = to_montgomery_value(x, p, limb_bits)
return '"%x"' % value
def to_montgomery(x, p):
@ -296,25 +299,16 @@ def modinv(a, m):
def format_curve_name(g):
return "p%d" % g["q"].bit_length()
def format_prime_curve(g):
def generate_rs(g, out_dir):
q = g["q"]
n = g["n"]
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 q != g["q_formula"]:
raise ValueError("Polynomial representation of q doesn't match the "
"literal version given in the specification.")
if n != g["n_formula"]:
raise ValueError("Polynomial representation of n doesn't match the "
"literal version given in the specification.")
name = format_curve_name(g)
q_minus_3 = "\\\n// ".join(wrap(hex(q - 3), 66))
n_minus_2 = "\\\n// ".join(wrap(hex(n - 2), 66))
return curve_template % {
output = rs_template % {
"bits": g["q"].bit_length(),
"name": name,
"q" : q,
@ -331,6 +325,133 @@ def format_prime_curve(g):
"n_minus_2": n_minus_2,
}
out_path = os.path.join(out_dir, "%s.rs" % name)
with open(out_path, "wb") as f:
f.write(output.encode("utf-8"))
subprocess.run(["rustfmt", out_path])
c_template = """
/* Copyright 2016-2023 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 AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 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. */
#include "../../limbs/limbs.h"
#include "../bn/internal.h"
#include "../../internal.h"
#include "../../limbs/limbs.inl"
typedef Limb Elem[P%(bits)d_LIMBS];
typedef Limb ScalarMont[P%(bits)d_LIMBS];
typedef Limb Scalar[P%(bits)d_LIMBS];
static const BN_ULONG Q[P%(bits)d_LIMBS] = {
%(q)s
};
static const BN_ULONG N[P%(bits)d_LIMBS] = {
%(n)s
};
static const BN_ULONG ONE[P%(bits)d_LIMBS] = {
%(q_one)s
};
static const Elem Q_PLUS_1_SHR_1 = {
%(q_plus_1_shr_1)s
};
static const BN_ULONG Q_N0[] = {
%(q_n0)s
};
static const BN_ULONG N_N0[] = {
%(n_n0)s
};
"""
# 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):
if x < 0:
raise ValueError("x must be positive");
if x == 0:
yield [0]
return
while x != 0:
x, digit = divmod(x, 2**64)
yield digit
def format_bn(x, tobn):
def format_limb(val):
if val < 10:
return "%d" % val
else:
return "0x%x" % val
hi = format_limb(x // (2**32))
lo = format_limb(x % (2**32))
return "%s(%s, %s)" % (tobn, hi, lo)
tobn = lambda limb: format_bn(limb, "TOBN")
bn_mont_ctx_n0 = lambda limb: format_bn(limb, "BN_MONT_CTX_N0")
def format_big_int(x, limb_count, f = tobn):
limbs = list(little_endian_limbs(x))
limbs += (limb_count - len(limbs)) * [0]
return ",\n ".join([f(limb) for limb in limbs])
def generate_c(g, out_dir):
q = g["q"]
n = g["n"]
name = format_curve_name(g)
limb_bits = 64
limb_count = (q.bit_length() + limb_bits - 1) // limb_bits
output = c_template % {
"bits": q.bit_length(),
"q" : format_big_int(q, limb_count),
"q_n0": format_big_int(modinv(-q, 2**limb_bits), 1, bn_mont_ctx_n0),
"q_one" : format_big_int(to_montgomery_value(1, q, limb_bits), limb_count),
"q_plus_1_shr_1": format_big_int((q + 1) >> 1, limb_count),
"n" : format_big_int(n, limb_count),
"n_n0": format_big_int(modinv(-n, 2**64), 1, bn_mont_ctx_n0),
}
out_path = os.path.join(out_dir, "gfp_%s.c" % name)
with open(out_path, "wb") as f:
f.write(output.encode("utf-8"))
def generate(g, out_dir):
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["q_formula"]:
raise ValueError("Polynomial representation of q doesn't match the "
"literal version given in the specification.")
if g["n"] != g["n_formula"]:
raise ValueError("Polynomial representation of n doesn't match the "
"literal version given in the specification.")
generate_rs(g, out_dir)
generate_c(g, out_dir)
# The curve parameters are from
# https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf.
# |q_formula| and |n_formula| are given in the polynomial representation so that
@ -377,14 +498,7 @@ p521 = {
import os
import subprocess
def generate_prime_curve_file(g, out_dir):
code = format_prime_curve(g)
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(out_dir, "%s.rs" % format_curve_name(g))
with open(out_path, "wb") as f:
f.write(code.encode("utf-8"))
subprocess.run(["rustfmt", out_path])
out_dir = "target/curves"
os.makedirs(out_dir, exist_ok=True)
for curve in [p256, p384, p521]:
generate_prime_curve_file(curve, "target/curves")
generate(curve, out_dir)