From 71fba5c6662d6bbe74366c03b531a5f86b25bbbb Mon Sep 17 00:00:00 2001 From: "Pavel V. Shatov (Meister)" Date: Mon, 26 Feb 2018 13:05:17 +0300 Subject: Initial commit of C reference model for ECDH cores. --- test_vectors/format_test_vectors.py | 450 ++++++++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 test_vectors/format_test_vectors.py (limited to 'test_vectors/format_test_vectors.py') diff --git a/test_vectors/format_test_vectors.py b/test_vectors/format_test_vectors.py new file mode 100644 index 0000000..a49b34b --- /dev/null +++ b/test_vectors/format_test_vectors.py @@ -0,0 +1,450 @@ +# +# format_test_vectors.py +# ------------------------------------------ +# Formats test vectors for ecdsa_fpga_model +# +# Author: Pavel Shatov +# Copyright (c) 2017, NORDUnet A/S +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# - Neither the name of the NORDUnet nor the names of its contributors may +# be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# +# This script reads the test vectors generated by regenerate_test_vectors.py +# and writes nicely formatted C header file and Verilog include file. +# + +# +# imports +# +import sys +import subprocess +from fastecdsa.curve import P256 +from fastecdsa.curve import P384 +from fastecdsa.point import Point + +# list of curve names of interest +CURVE_P256 = "p256" +CURVE_P384 = "p384" + +# the base point for p-256 +P256_GX = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 +P256_GY = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 + +# the base point for p-384 +P384_GX = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7 +P384_GY = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f + + +# +# get part of string between two markers +# +#def string_between(s, s_left, s_right): +# s_begin = s.index(s_left) + len(s_left) +# s_end = s.index(s_right, s_begin) +# return s[s_begin:s_end] + +# +# load message from file +# +#def read_message(key): +# with open(key + ".txt", "r") as f: +# return f.readlines()[0] +# +# +# read modulus from file +# +#def read_modulus(key): +# openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-modulus"] +# openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") +# return openssl_stdout.strip().split("=")[1] + +# +# read private exponent from file +# +#def read_secret(key): +# openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] +# openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") +# openssl_secret = string_between(openssl_stdout, "privateExponent", "prime1") +# openssl_secret = openssl_secret.replace(":", "") +# openssl_secret = openssl_secret.replace("\n", "") +# openssl_secret = openssl_secret.replace(" ", "") +# return openssl_secret + +# +# read part of private key from file +# +#def read_prime1(key): +# openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] +# openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") +# openssl_secret = string_between(openssl_stdout, "prime1", "prime2") +# openssl_secret = openssl_secret.replace(":", "") +# openssl_secret = openssl_secret.replace("\n", "") +# openssl_secret = openssl_secret.replace(" ", "") +# return openssl_secret +#def read_prime2(key): +# openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] +# openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") +# openssl_secret = string_between(openssl_stdout, "prime2", "exponent1") +# openssl_secret = openssl_secret.replace(":", "") +# openssl_secret = openssl_secret.replace("\n", "") +# openssl_secret = openssl_secret.replace(" ", "") +# return openssl_secret + +# +# read prive exponent from file +# +#def read_exponent1(key): +# openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] +# openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") +# openssl_secret = string_between(openssl_stdout, "exponent1", "exponent2") +# openssl_secret = openssl_secret.replace(":", "") +# openssl_secret = openssl_secret.replace("\n", "") +# openssl_secret = openssl_secret.replace(" ", "") +# return openssl_secret +#def read_exponent2(key): +# openssl_command = ["openssl", "rsa", "-in", key + ".key", "-noout", "-text"] +# openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") +# openssl_secret = string_between(openssl_stdout, "exponent2", "coefficient") +# openssl_secret = openssl_secret.replace(":", "") +# openssl_secret = openssl_secret.replace("\n", "") +# openssl_secret = openssl_secret.replace(" ", "") +# return openssl_secret + +# +# format one test vector +# +def format_c_header(f, curve, da, qax, qay, db, qbx, qby, sx, sy): + + if curve == CURVE_P256: curve_str = "P_256" + if curve == CURVE_P384: curve_str = "P_384" + + # write all numbers in vector + format_c_array(f, da, "#define " + curve_str + "_DA" + " \\\n") + format_c_array(f, qax, "#define " + curve_str + "_QA_X" + " \\\n") + format_c_array(f, qay, "#define " + curve_str + "_QA_Y" + " \\\n") + + format_c_array(f, db, "#define " + curve_str + "_DB" + " \\\n") + format_c_array(f, qbx, "#define " + curve_str + "_QB_X" + " \\\n") + format_c_array(f, qby, "#define " + curve_str + "_QB_Y" + " \\\n") + + format_c_array(f, sx, "#define " + curve_str + "_S_X" + " \\\n") + format_c_array(f, sy, "#define " + curve_str + "_S_Y" + " \\\n") + + +# +# format one test vector +# +#def format_verilog_include(f, key, n, m, d, s, p, q, dp, dq, mp, mq): +# +# # calculate factor to bring message into Montgomery domain +# factor = calc_montgomery_factor(int(key), n) +# factor_p = calc_montgomery_factor(int(key)//2, p); +# factor_q = calc_montgomery_factor(int(key)//2, q); +# +# # calculate helper coefficients for Montgomery multiplication +# n_coeff = calc_montgomery_n_coeff(int(key), n) +# p_coeff = calc_montgomery_n_coeff(int(key)//2, p) +# q_coeff = calc_montgomery_n_coeff(int(key)//2, q) +# +# # calculate the extra coefficient Montgomery multiplication brings in +# coeff = modinv(1 << int(key), n) +# +# # convert m into Montgomery representation +# m_factor = (m * factor * coeff) % n +# +# # write all numbers +# format_verilog_concatenation(f, m, "localparam [" + str(int(key)-1) + ":0] M_" + key + " =\n") +# format_verilog_concatenation(f, n, "localparam [" + str(int(key)-1) + ":0] N_" + key + " =\n") +# format_verilog_concatenation(f, n_coeff, "localparam [" + str(int(key)-1) + ":0] N_COEFF_" + key + " =\n") +# format_verilog_concatenation(f, factor, "localparam [" + str(int(key)-1) + ":0] FACTOR_" + key + " =\n") +# format_verilog_concatenation(f, coeff, "localparam [" + str(int(key)-1) + ":0] COEFF_" + key + " =\n") +# format_verilog_concatenation(f, m_factor, "localparam [" + str(int(key)-1) + ":0] M_FACTOR_" + key + " =\n") +# format_verilog_concatenation(f, d, "localparam [" + str(int(key)-1) + ":0] D_" + key + " =\n") +# format_verilog_concatenation(f, s, "localparam [" + str(int(key)-1) + ":0] S_" + key + " =\n") +# +# format_verilog_concatenation(f, p, "localparam [" + str(int(key)//2-1) + ":0] P_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, q, "localparam [" + str(int(key)//2-1) + ":0] Q_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, p_coeff, "localparam [" + str(int(key)//2-1) + ":0] P_COEFF_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, q_coeff, "localparam [" + str(int(key)//2-1) + ":0] Q_COEFF_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, factor_p, "localparam [" + str(int(key)//2-1) + ":0] FACTOR_P_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, factor_q, "localparam [" + str(int(key)//2-1) + ":0] FACTOR_Q_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, dp, "localparam [" + str(int(key)//2-1) + ":0] DP_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, dq, "localparam [" + str(int(key)//2-1) + ":0] DQ_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, mp, "localparam [" + str(int(key)//2-1) + ":0] MP_" + str(int(key)//2) + " =\n") +# format_verilog_concatenation(f, mq, "localparam [" + str(int(key)//2-1) + ":0] MQ_" + str(int(key)//2) + " =\n") + + +# +# nicely format multi-word integer into C array initializer +# +def format_c_array(f, n, s): + + # print '#define XYZ \' + f.write(s) + + # convert number to hex string and prepend it with zeroes if necessary + n_hex = hex(n).lstrip("0x").rstrip("L") + while (len(n_hex) % 8) > 0: + n_hex = "0" + n_hex + + # get number of 32-bit words + num_words = len(n_hex) // 8 + + # print all words in n + w = 0 + while w < num_words: + + n_part = "" + + # add tab for every new line + if w == 0: + n_part += "\t{" + elif (w % 4) == 0: + n_part += "\t " + + # add current word + n_part += "0x" + n_hex[8 * w : 8 * (w + 1)] + + # add separator or newline + if (w + 1) == num_words: + n_part += "}\n" + else: + n_part += ", " + if (w % 4) == 3: + n_part += "\\\n" + + w += 1 + + # write current part + f.write(n_part) + + # write final newline + f.write("\n") + + +#def format_verilog_concatenation(f, n, s): +# +# # print 'localparam ZZZ =' +# f.write(s) +# +# # convert number to hex string and prepend it with zeroes if necessary +# n_hex = hex(n).split("0x")[1] +# while (len(n_hex) % 8) > 0: +# n_hex = "0" + n_hex +# +# # get number of 32-bit words +# num_words = len(n_hex) // 8 +# +# # print all words in n +# w = 0 +# while w < num_words: +# +# n_part = "" +# +# if w == 0: +# n_part += "\t{" +# elif (w % 4) == 0: +# n_part += "\t " +# +# n_part += "32'h" + n_hex[8 * w : 8 * (w + 1)] +# +# if (w + 1) == num_words: +# n_part += "};\n" +# else: +# n_part += ", " +# if (w % 4) == 3: +# n_part += "\n" +# w += 1 +# +# f.write(n_part) +# +# f.write("\n") + + + # + # returns d, qx, qy, where + # d is private key and qx, qy is the corresponding public key + # +def get_key(party, curve): + + # generate private key filename + key_file = party + "_" + curve + ".key" + + # retrieve key components using openssl + openssl_command = ["openssl", "ec", "-in", key_file, "-noout", "-text"] + openssl_stdout = subprocess.check_output(openssl_command).decode("utf-8") + stdout_lines = openssl_stdout.splitlines() + + found_priv = False + found_pub = False + + key_priv = "" + key_pub = "" + + # process lines looking for "priv:" and "pub:" markers + for line in stdout_lines: + + # found private key marker? + if line.strip() == "priv:": + found_priv = True + found_pub = False + continue + + # found public key marker? + if line.strip() == "pub:": # openssl 1.0.2g prints 'pub: ' (extra space before newline), + found_pub = True # so we need compare against line.strip(), not just line + found_priv = False + continue + + # found part of private key? + if found_priv: + if not line.startswith(" "): + found_priv = False + continue + else: + key_priv += line.strip() + + # found part of public key? + if found_pub: + if not line.startswith(" "): + found_pub = False + continue + else: + key_pub += line.strip() + + # do some cleanup and sanity checking on private key + # * remove extra leading zero byte if present + # * remove colons + # * check length (256 bits or 384 bits) + while key_priv.startswith("00"): + key_priv = key_priv[2:] + + key_priv = key_priv.replace(":", "") + + if curve == CURVE_P256 and len(key_priv) != 256 / 4: sys.exit() + if curve == CURVE_P384 and len(key_priv) != 384 / 4: sys.exit() + + # do some cleanup and sanity checking on public key + # * make sure, that uncompressed form marker (0x04) is present and + # then remove it + # * remove colons + # * check length (2x256 or 2x384 bits) + if not key_pub.startswith("04"): sys.exit() + + key_pub = key_pub[2:] + key_pub = key_pub.replace(":", "") + + if curve == CURVE_P256 and len(key_pub) != 2 * 256 / 4: sys.exit() + if curve == CURVE_P384 and len(key_pub) != 2 * 384 / 4: sys.exit() + + # split public key into parts + if curve == CURVE_P256: + key_pub_x = key_pub[ 0: 64] + key_pub_y = key_pub[ 64:128] + + if curve == CURVE_P384: + key_pub_x = key_pub[ 0: 96] + key_pub_y = key_pub[ 96:192] + + # convert from strings to integers + key_priv = int(key_priv, 16) + key_pub_x = int(key_pub_x, 16) + key_pub_y = int(key_pub_y, 16) + + # another sanity check (make sure, that Q is actually d * G) + if curve == CURVE_P256: + G = Point(P256_GX, P256_GY, curve=P256) + Q = Point(key_pub_x, key_pub_y, curve=P256) + + if curve == CURVE_P384: + G = Point(P384_GX, P384_GY, curve=P384) + Q = Point(key_pub_x, key_pub_y, curve=P384) + + # multiply using fastecdsa + R = key_priv * G + + if R.x != Q.x: sys.exit() + if R.y != Q.y: sys.exit() + + # done + return key_priv, key_pub_x, key_pub_y + + +if __name__ == "__main__": + + # list of curves to process + curves = [CURVE_P256, CURVE_P384] + + # open output files + file_h = open('ecdsa_fpga_model_ecdh_vectors.h', 'w') +# file_v = open('modexp_fpga_model_vectors.v', 'w') + + # write headers + file_h.write("/* Generated automatically, do not edit. */\n\n") +# file_v.write("/* Generated automatically, do not edit. */\n\n") + + # process all the keys + for curve in curves: + + # load keys + da, qax, qay = get_key("alice", curve) + db, qbx, qby = get_key("bob", curve) + + # Alice's public key + if (curve == CURVE_P256): QA = Point(qax, qay, curve=P256) + if (curve == CURVE_P384): QA = Point(qax, qay, curve=P384) + + # Bob's public key + if (curve == CURVE_P256): QB = Point(qbx, qby, curve=P256) + if (curve == CURVE_P384): QB = Point(qbx, qby, curve=P384) + + # we derive the shared secret two different ways (from Alice's and + # from Bob's perspective, they must be identical of course + QAB = da * QB # Alice's secret + QBA = db * QA # Bob's secret + + if (QAB.x != QBA.x): sys.exit() + if (QBA.y != QAB.y): sys.exit() + + print("Derived shared secret."); + + # format numbers and write to file + format_c_header(file_h, curve, da, qax, qay, db, qbx, qby, QAB.x, QBA.y) +# format_verilog_include(file_v, key, modulus, message, secret, signature, prime1, prime2, exponent1, exponent2, message1, message2) + + # done + file_h.close() +# file_v.close() + + # everything went just fine + print("Test vectors formatted.") + +# +# End of file +# -- cgit v1.2.3