carlos / contra Public

Clone
git clone https://amadan.net/carlos/contra

Plain git — no account needed to clone.

Download

Download this file

1/*
2 * seedsearch — find a seed whose first P-256 candidate is out of range.
3 *
4 * PROTOCOL.md requires rejection sampling mod n when deriving a P-256 scalar
5 * from a seed, and discussion #6 requires a vector that actually exercises
6 * the rejection branch: "pin a seed that requires a rejection, or the branch
7 * goes untested". A rejection happens with probability about 2^-32, so no
8 * seed anybody picks by hand will ever hit it. This searches for one.
9 *
10 * The search space is deliberately reproducible rather than random:
11 *
12 * seed_k = "contra/v1/vector-search!" || u64be(k) (32 bytes)
13 *
14 * so the witness is a single number: the vector file pins k, and anyone can
15 * rebuild the seed from it without trusting this program. It also makes the
16 * seed visibly generated rather than plausibly a real key. (Threads race, so
17 * the k reported is a witness, not necessarily the smallest one.)
18 *
19 * A hit is a k where OS2IP(candidate_0) is 0 or >= n, with
20 *
21 * candidate_0 = HKDF-SHA-256(IKM = seed_k, salt = "",
22 * info = "contra/v1/ec-p256" || 0x00)
23 *
24 * This tool is a helper, not an authority. Whatever it finds is re-derived
25 * and re-checked by gen.c against OpenSSL before it reaches the vector file.
26 *
27 * Build: make seedsearch Run: ./seedsearch [threads]
28 */
29#define OPENSSL_SUPPRESS_DEPRECATED 1
30
31#include <inttypes.h>
32#include <pthread.h>
33#include <stdint.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37
38#include <openssl/sha.h>
39
40#define SEED_PREFIX "contra/v1/vector-search!" /* 24 bytes, no NUL */
41#define SEED_PREFIX_LEN 24
42#define EC_LABEL "contra/v1/ec-p256"
43#define EC_LABEL_LEN 17
44
45/* P-256 group order n, big-endian. */
46static const unsigned char P256_N[32] = {
47 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
48 0xff, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17,
49 0x9e, 0x84, 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51};
50
51/* HMAC-SHA-256 over a key of at most 64 bytes. Enough for this search. */
52static void hmac_sha256(const unsigned char *key, size_t keylen,
53 const unsigned char *msg, size_t msglen,
54 unsigned char out[32]) {
55 unsigned char ipad[64], opad[64], inner[32];
56 SHA256_CTX c;
57 size_t i;
58
59 memset(ipad, 0x36, sizeof ipad);
60 memset(opad, 0x5c, sizeof opad);
61 for (i = 0; i < keylen; i++) {
62 ipad[i] ^= key[i];
63 opad[i] ^= key[i];
64 }
65
66 SHA256_Init(&c);
67 SHA256_Update(&c, ipad, sizeof ipad);
68 SHA256_Update(&c, msg, msglen);
69 SHA256_Final(inner, &c);
70
71 SHA256_Init(&c);
72 SHA256_Update(&c, opad, sizeof opad);
73 SHA256_Update(&c, inner, sizeof inner);
74 SHA256_Final(out, &c);
75}
76
77/* True when the candidate is not a usable P-256 scalar: zero, or >= n. */
78static int out_of_range(const unsigned char c[32]) {
79 int i, nonzero = 0;
80
81 for (i = 0; i < 32; i++)
82 if (c[i]) {
83 nonzero = 1;
84 break;
85 }
86 if (!nonzero) return 1;
87
88 for (i = 0; i < 32; i++) {
89 if (c[i] < P256_N[i]) return 0;
90 if (c[i] > P256_N[i]) return 1;
91 }
92 return 1; /* exactly n */
93}
94
95static volatile int g_found = 0;
96static uint64_t g_found_k = 0;
97static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
98
99struct span {
100 uint64_t start, stride, limit;
101};
102
103static void *worker(void *arg) {
104 struct span *s = arg;
105 unsigned char seed[32], prk[32], info[EC_LABEL_LEN + 2], cand[32];
106 unsigned char zero_salt[32];
107 uint64_t k;
108
109 memset(zero_salt, 0, sizeof zero_salt);
110 memcpy(seed, SEED_PREFIX, SEED_PREFIX_LEN);
111 memcpy(info, EC_LABEL, EC_LABEL_LEN);
112 info[EC_LABEL_LEN] = 0x00; /* counter i = 0 */
113 info[EC_LABEL_LEN + 1] = 0x01; /* HKDF-Expand block counter T(1) */
114
115 for (k = s->start; k < s->limit && !g_found; k += s->stride) {
116 int i;
117 for (i = 0; i < 8; i++) seed[24 + i] = (unsigned char)(k >> (56 - 8 * i));
118
119 /* HKDF-Extract with a zero-length salt: RFC 5869 substitutes
120 * HashLen zero bytes as the HMAC key. */
121 hmac_sha256(zero_salt, sizeof zero_salt, seed, sizeof seed, prk);
122 /* HKDF-Expand, first and only 32-byte block. */
123 hmac_sha256(prk, sizeof prk, info, sizeof info, cand);
124
125 /* Cheap filter first: a candidate >= n must open 0xFFFFFFFF. */
126 if (cand[0] != 0xff || cand[1] != 0xff || cand[2] != 0xff ||
127 cand[3] != 0xff)
128 continue;
129
130 if (out_of_range(cand)) {
131 pthread_mutex_lock(&g_mu);
132 if (!g_found || k < g_found_k) g_found_k = k;
133 g_found = 1;
134 pthread_mutex_unlock(&g_mu);
135 return NULL;
136 }
137 }
138 return NULL;
139}
140
141int main(int argc, char **argv) {
142 long nthreads = (argc > 1) ? strtol(argv[1], NULL, 10) : 8;
143 pthread_t *th;
144 struct span *spans;
145 long i;
146
147 if (nthreads < 1) nthreads = 1;
148 th = calloc((size_t)nthreads, sizeof *th);
149 spans = calloc((size_t)nthreads, sizeof *spans);
150 if (!th || !spans) return 1;
151
152 for (i = 0; i < nthreads; i++) {
153 spans[i].start = (uint64_t)i;
154 spans[i].stride = (uint64_t)nthreads;
155 spans[i].limit = UINT64_C(1) << 40;
156 pthread_create(&th[i], NULL, worker, &spans[i]);
157 }
158 for (i = 0; i < nthreads; i++) pthread_join(th[i], NULL);
159
160 if (!g_found) {
161 fprintf(stderr, "seedsearch: no rejecting seed found\n");
162 return 1;
163 }
164 printf("%" PRIu64 "\n", g_found_k);
165 return 0;
166}
167