#
# 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
#
# 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, curve, da, qax, qay, db, qbx, qby, sx, sy):
if curve == CURVE_P256:
curve_str = "P_256"
msb_index = "255"
if curve == CURVE_P384:
curve_str = "P_384"
msb_index = "383"
# write all numbers in vector
format_verilog_concatenation(f, da, "localparam [" + msb_index + ":0] " + curve_str + "_DA" + " =\n")
format_verilog_concatenation(f, qax, "localparam [" + msb_index + ":0] " + curve_str + "_QA_X" + " =\n")
format_verilog_concatenation(f, qay, "localparam [" + msb_index + ":0] " + curve_str + "_QA_Y" + " =\n")
format_verilog_concatenation(f, db, "localparam [" + msb_index + ":0] " + curve_str + "_DB" + " =\n")
format_verilog_concatenation(f, qbx, "localparam [" + msb_index + ":0] " + curve_str + "_QB_X" + " =\n")
format_verilog_concatenation(f, qby, "localparam [" + msb_index + ":0] " + curve_str + "_QB_Y" + " =\n")
format_verilog_concatenation(f, sx, "localparam [" + msb_index + ":0] " + curve_str + "_S_X" + " =\n")
format_verilog_concatenation(f, sy, "localparam [" + msb_index + ":0] " + curve_str + "_S_Y" + " =\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('ecdh_test_vectors.h', 'w')
file_v = open('ecdh_test_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, curve, da, qax, qay, db, qbx, qby, QAB.x, QBA.y)
# done
file_h.close()
file_v.close()
# everything went just fine
print("Test vectors formatted.")
#
# End of file
#