aboutsummaryrefslogtreecommitdiff
path: root/test_vectors/format_test_vectors.py
diff options
context:
space:
mode:
Diffstat (limited to 'test_vectors/format_test_vectors.py')
-rw-r--r--test_vectors/format_test_vectors.py450
1 files changed, 450 insertions, 0 deletions
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
+#