File: tests_wycheproof_generate_ecdh.py

package info (click to toggle)
libsecp256k1 0.7.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 6,368 kB
  • sloc: ansic: 44,731; python: 772; asm: 736; makefile: 282; sh: 227
file content (166 lines) | stat: -rwxr-xr-x 5,964 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python3
# Copyright (c) 2024 Random "Randy" Lattice and Sean Andersen
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php.
'''
Generate a C file with ECDH testvectors from the Wycheproof project.
'''

import json
import sys

from binascii import hexlify, unhexlify
from wycheproof_utils import to_c_array

def should_skip_flags(test_vector_flags):
    # skip these vectors because they are for ASN.1 encoding issues and other curves.
    # for more details, see https://github.com/bitcoin-core/secp256k1/pull/1492#discussion_r1572491546
    flags_to_skip = {"InvalidAsn", "WrongCurve"}
    return any(flag in test_vector_flags for flag in flags_to_skip)

def should_skip_tcid(test_vector_tcid):
    # We skip some test case IDs that have a public key whose custom ASN.1 representation explicitly
    # encodes some curve parameters that are invalid. libsecp256k1 never parses this part so we do
    # not care testing those. See https://github.com/bitcoin-core/secp256k1/pull/1492#discussion_r1572491546
    tcids_to_skip = [496, 497, 502, 503, 504, 505, 507]
    return test_vector_tcid in tcids_to_skip

# Rudimentary ASN.1 DER public key parser.
# This should not be used for anything other than parsing Wycheproof test vectors.
def parse_der_pk(s):
    tag = s[0]
    L = int(s[1])
    offset = 0
    if L & 0x80:
        if L == 0x81:
            L = int(s[2])
            offset = 1
        elif L == 0x82:
            L = 256 * int(s[2]) + int(s[3])
            offset = 2
        else:
            raise ValueError("invalid L")
    value = s[(offset + 2):(L + 2 + offset)]
    rest = s[(L + 2 + offset):]

    if len(rest) > 0 or tag == 0x06: # OBJECT IDENTIFIER
        return parse_der_pk(rest)
    if tag == 0x03: # BIT STRING
        return value
    if tag == 0x30: # SEQUENCE
        return parse_der_pk(value)
    raise ValueError("unknown tag")

def parse_public_key(pk):
    der_pub_key = parse_der_pk(unhexlify(pk))   # Convert back to str and strip off the `0x`
    return hexlify(der_pub_key).decode()[2:]

def normalize_private_key(sk):
    # Ensure the private key is at most 64 characters long, retaining the last 64 if longer.
    # In the wycheproof test vectors, some private keys have leading zeroes
    normalized = sk[-64:].zfill(64)
    if len(normalized) != 64:
        raise ValueError("private key must be exactly 64 characters long.")
    return normalized

def normalize_expected_result(er):
    result_mapping = {"invalid": 0, "valid": 1, "acceptable": 1}
    return result_mapping[er]

filename_input = sys.argv[1]

with open(filename_input) as f:
    doc = json.load(f)

num_vectors = 0
offset_sk_running, offset_pk_running, offset_shared = 0, 0, 0
test_vectors_out = ""
private_keys = ""
shared_secrets = ""
public_keys = ""
cache_sks = {}
cache_public_keys = {}

for group in doc['testGroups']:
    assert group["type"] == "EcdhTest"
    assert group["curve"] == "secp256k1"
    for test_vector in group['tests']:
        if should_skip_flags(test_vector['flags']) or should_skip_tcid(test_vector['tcId']):
            continue

        public_key = parse_public_key(test_vector['public'])
        private_key = normalize_private_key(test_vector['private'])
        expected_result = normalize_expected_result(test_vector['result'])

        # // 2 to convert hex to byte length
        shared_size = len(test_vector['shared']) // 2
        sk_size = len(private_key) // 2
        pk_size = len(public_key) // 2

        new_sk = False
        sk = to_c_array(private_key)
        sk_offset = offset_sk_running

        # check for repeated sk
        if sk not in cache_sks:
            if num_vectors != 0 and sk_size != 0:
                private_keys += ",\n  "
            cache_sks[sk] = offset_sk_running
            private_keys += sk
            new_sk = True
        else:
            sk_offset = cache_sks[sk]

        new_pk = False
        pk = to_c_array(public_key) if public_key != '0x' else ''

        pk_offset = offset_pk_running
        # check for repeated pk
        if pk not in cache_public_keys:
            if num_vectors != 0 and len(pk) != 0:
                public_keys += ",\n  "
            cache_public_keys[pk] = offset_pk_running
            public_keys += pk
            new_pk = True
        else:
            pk_offset = cache_public_keys[pk]


        shared_secrets += ",\n  " if num_vectors and shared_size else ""
        shared_secrets += to_c_array(test_vector['shared'])
        wycheproof_tcid = test_vector['tcId']

        test_vectors_out += "  /" + "* tcId: " + str(test_vector['tcId']) + ". " + test_vector['comment'] + " *" + "/\n"
        test_vectors_out += f"  {{{pk_offset}, {pk_size}, {sk_offset}, {sk_size}, {offset_shared}, {shared_size}, {expected_result}, {wycheproof_tcid} }},\n"
        if new_sk:
            offset_sk_running += sk_size
        if new_pk:
            offset_pk_running += pk_size
        offset_shared += shared_size
        num_vectors += 1

struct_definition = """
typedef struct {
    size_t pk_offset;
    size_t pk_len;
    size_t sk_offset;
    size_t sk_len;
    size_t shared_offset;
    size_t shared_len;
    int expected_result;
    int wycheproof_tcid;
} wycheproof_ecdh_testvector;
"""

print("/* Note: this file was autogenerated using tests_wycheproof_ecdh.py. Do not edit. */")
print(f"#define SECP256K1_ECDH_WYCHEPROOF_NUMBER_TESTVECTORS ({num_vectors})")

print(struct_definition)

print("static const unsigned char wycheproof_ecdh_private_keys[]    = { " + private_keys + "};\n")
print("static const unsigned char wycheproof_ecdh_public_keys[] = { " + public_keys + "};\n")
print("static const unsigned char wycheproof_ecdh_shared_secrets[]  = { " + shared_secrets + "};\n")

print("static const wycheproof_ecdh_testvector testvectors[SECP256K1_ECDH_WYCHEPROOF_NUMBER_TESTVECTORS] = {")
print(test_vectors_out)
print("};")