1
0

slh_wots.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /*
  2. * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
  3. *
  4. * Licensed under the Apache License 2.0 (the "License"). You may not use
  5. * this file except in compliance with the License. You can obtain a copy
  6. * in the file LICENSE in the source distribution or at
  7. * https://www.openssl.org/source/license.html
  8. */
  9. #include <string.h>
  10. #include <openssl/crypto.h>
  11. #include "slh_dsa_local.h"
  12. #include "slh_dsa_key.h"
  13. /* For the parameter sets defined there is only one w value */
  14. #define SLH_WOTS_LOGW 4
  15. #define SLH_WOTS_W 16
  16. #define SLH_WOTS_LEN1(n) (2 * (n))
  17. #define SLH_WOTS_LEN2 3
  18. #define SLH_WOTS_CHECKSUM_LEN ((SLH_WOTS_LEN2 + SLH_WOTS_LOGW + 7) / 8)
  19. #define SLH_WOTS_LEN_MAX SLH_WOTS_LEN(SLH_MAX_N)
  20. #define NIBBLE_MASK 15
  21. #define NIBBLE_SHIFT 4
  22. /*
  23. * @brief Convert a byte array to a byte array of (4 bit) nibbles
  24. * This is a Variant of the FIPS 205 Algorithm 4 base_2^b function.
  25. *
  26. * @param in A byte message to convert
  27. * @param in_len The size of |in|.
  28. * @param out The returned array of nibbles, with a size of 2*|in_len|
  29. */
  30. static ossl_inline void slh_bytes_to_nibbles(const uint8_t *in, size_t in_len,
  31. uint8_t *out)
  32. {
  33. size_t consumed = 0;
  34. for (consumed = 0; consumed < in_len; consumed++) {
  35. *out++ = (*in >> NIBBLE_SHIFT);
  36. *out++ = (*in++ & NIBBLE_MASK);
  37. }
  38. }
  39. /*
  40. * With w = 16 the maximum checksum is 0xF * n which fits into 12 bits
  41. * which is 3 nibbles.
  42. *
  43. * This is effectively a cutdown version of Algorithm 7: steps 3 to 6
  44. * which does a complicated base2^b(tobyte()) operation.
  45. */
  46. static ossl_inline void compute_checksum_nibbles(const uint8_t *in, size_t in_len,
  47. uint8_t *out)
  48. {
  49. size_t i;
  50. uint16_t csum = 0;
  51. /* Compute checksum */
  52. for (i = 0; i < in_len; ++i)
  53. csum += in[i];
  54. /*
  55. * This line is effectively the same as doing csum += NIBBLE_MASK - in[i]
  56. * in the loop above.
  57. */
  58. csum = (uint16_t)(NIBBLE_MASK * in_len) - csum;
  59. /* output checksum as 3 nibbles */
  60. out[0] = (csum >> (2 * NIBBLE_SHIFT)) & NIBBLE_MASK;
  61. out[1] = (csum >> NIBBLE_SHIFT) & NIBBLE_MASK;
  62. out[2] = csum & NIBBLE_MASK;
  63. }
  64. /**
  65. * @brief WOTS+ Chaining function
  66. * See FIPS 205 Section 5 Algorithm 5
  67. *
  68. * Iterates using a hash function on the input |steps| times starting at index
  69. * |start|. (Internally the |adrs| hash address is used to update the chaining
  70. * index).
  71. *
  72. * @param ctx Contains SLH_DSA algorithm functions and constants.
  73. * @param in An input string of |n| bytes
  74. * @param start_index The chaining start index
  75. * @param steps The number of iterations starting from |start_index|
  76. * Note |start_index| + |steps| < w
  77. * (where w = 16 indicates the length of the hash chains)
  78. * @param pk_seed A public key seed (which is added to the hash)
  79. * @param adrs An ADRS object which has a type of WOTS_HASH, and has a layer
  80. * address, tree address, key pair address and chain address
  81. * @params wpkt A WPACKET object to write the hash chain to (n bytes are written)
  82. * @returns 1 on success, or 0 on error.
  83. */
  84. static int slh_wots_chain(SLH_DSA_HASH_CTX *ctx, const uint8_t *in,
  85. uint8_t start_index, uint8_t steps,
  86. const uint8_t *pk_seed, uint8_t *adrs, WPACKET *wpkt)
  87. {
  88. const SLH_DSA_KEY *key = ctx->key;
  89. SLH_HASH_FUNC_DECLARE(key, hashf);
  90. SLH_ADRS_FUNC_DECLARE(key, adrsf);
  91. SLH_HASH_FN_DECLARE(hashf, F);
  92. SLH_ADRS_FN_DECLARE(adrsf, set_hash_address);
  93. size_t j = start_index, end_index;
  94. size_t n = key->params->n;
  95. uint8_t *tmp; /* Pointer into the |wpkt| buffer */
  96. size_t tmp_len = n;
  97. if (steps == 0)
  98. return WPACKET_memcpy(wpkt, in, n);
  99. if (!WPACKET_allocate_bytes(wpkt, tmp_len, &tmp))
  100. return 0;
  101. set_hash_address(adrs, j++);
  102. if (!F(ctx, pk_seed, adrs, in, n, tmp, tmp_len))
  103. return 0;
  104. end_index = start_index + steps;
  105. for (; j < end_index; ++j) {
  106. set_hash_address(adrs, j);
  107. if (!F(ctx, pk_seed, adrs, tmp, n, tmp, tmp_len))
  108. return 0;
  109. }
  110. return 1;
  111. }
  112. /**
  113. * @brief WOTS+ Public key generation.
  114. * See FIPS 205 Section 5.1 Algorithm 6
  115. *
  116. * @param ctx Contains SLH_DSA algorithm functions and constants.
  117. * @param sk_seed A private key seed of size |n|
  118. * @param pk_seed A public key seed of size |n|
  119. * @param adrs An ADRS object containing the layer address, tree address and
  120. * keypair address of the WOTS+ public key to generate.
  121. * @param pk_out The generated public key of size |n|
  122. * @param pk_out_len The maximum size of |pk_out|
  123. * @returns 1 on success, or 0 on error.
  124. */
  125. int ossl_slh_wots_pk_gen(SLH_DSA_HASH_CTX *ctx,
  126. const uint8_t *sk_seed, const uint8_t *pk_seed,
  127. uint8_t *adrs, uint8_t *pk_out, size_t pk_out_len)
  128. {
  129. int ret = 0;
  130. const SLH_DSA_KEY *key = ctx->key;
  131. size_t n = key->params->n;
  132. size_t i, len = SLH_WOTS_LEN(n); /* 2 * n + 3 */
  133. uint8_t sk[SLH_MAX_N];
  134. uint8_t tmp[SLH_WOTS_LEN_MAX * SLH_MAX_N];
  135. WPACKET pkt, *tmp_wpkt = &pkt; /* Points to the |tmp| buffer */
  136. size_t tmp_len = 0;
  137. SLH_HASH_FUNC_DECLARE(key, hashf);
  138. SLH_ADRS_FUNC_DECLARE(key, adrsf);
  139. SLH_HASH_FN_DECLARE(hashf, PRF);
  140. SLH_ADRS_FN_DECLARE(adrsf, set_chain_address);
  141. SLH_ADRS_DECLARE(sk_adrs);
  142. SLH_ADRS_DECLARE(wots_pk_adrs);
  143. if (!WPACKET_init_static_len(tmp_wpkt, tmp, sizeof(tmp), 0))
  144. return 0;
  145. adrsf->copy(sk_adrs, adrs);
  146. adrsf->set_type_and_clear(sk_adrs, SLH_ADRS_TYPE_WOTS_PRF);
  147. adrsf->copy_keypair_address(sk_adrs, adrs);
  148. for (i = 0; i < len; ++i) { /* len = 2n + 3 */
  149. set_chain_address(sk_adrs, i);
  150. if (!PRF(ctx, pk_seed, sk_seed, sk_adrs, sk, sizeof(sk)))
  151. goto end;
  152. set_chain_address(adrs, i);
  153. if (!slh_wots_chain(ctx, sk, 0, NIBBLE_MASK, pk_seed, adrs, tmp_wpkt))
  154. goto end;
  155. }
  156. if (!WPACKET_get_total_written(tmp_wpkt, &tmp_len)) /* should be n * (2 * n + 3) */
  157. goto end;
  158. adrsf->copy(wots_pk_adrs, adrs);
  159. adrsf->set_type_and_clear(wots_pk_adrs, SLH_ADRS_TYPE_WOTS_PK);
  160. adrsf->copy_keypair_address(wots_pk_adrs, adrs);
  161. ret = hashf->T(ctx, pk_seed, wots_pk_adrs, tmp, tmp_len, pk_out, pk_out_len);
  162. end:
  163. WPACKET_finish(tmp_wpkt);
  164. OPENSSL_cleanse(tmp, sizeof(tmp));
  165. OPENSSL_cleanse(sk, n);
  166. return ret;
  167. }
  168. /**
  169. * @brief WOTS+ Signature generation
  170. * See FIPS 205 Section 5.2 Algorithm 7
  171. *
  172. * The returned signature size is len * |n| bytes (where len = 2 * |n| + 3).
  173. *
  174. * @param ctx Contains SLH_DSA algorithm functions and constants.
  175. * @param msg An input message of size |n| bytes.
  176. * The message is either an XMSS or FORS public key
  177. * @param sk_seed The private key seed of size |n| bytes
  178. * @param pk_seed The public key seed of size |n| bytes
  179. * @param adrs An address containing the layer address, tree address and key
  180. * pair address. The size is either 32 or 22 bytes.
  181. * @param sig_wpkt A WPACKET object to write the signature to.
  182. * @returns 1 on success, or 0 on error.
  183. */
  184. int ossl_slh_wots_sign(SLH_DSA_HASH_CTX *ctx, const uint8_t *msg,
  185. const uint8_t *sk_seed, const uint8_t *pk_seed,
  186. uint8_t *adrs, WPACKET *sig_wpkt)
  187. {
  188. int ret = 0;
  189. const SLH_DSA_KEY *key = ctx->key;
  190. uint8_t msg_and_csum_nibbles[SLH_WOTS_LEN_MAX]; /* size is >= 2 * n + 3 */
  191. uint8_t sk[SLH_MAX_N];
  192. size_t i;
  193. size_t n = key->params->n;
  194. size_t len1 = SLH_WOTS_LEN1(n); /* 2 * n = the msg length in nibbles */
  195. size_t len = len1 + SLH_WOTS_LEN2; /* 2 * n + 3 (3 checksum nibbles) */
  196. SLH_ADRS_DECLARE(sk_adrs);
  197. SLH_HASH_FUNC_DECLARE(key, hashf);
  198. SLH_ADRS_FUNC_DECLARE(key, adrsf);
  199. SLH_HASH_FN_DECLARE(hashf, PRF);
  200. SLH_ADRS_FN_DECLARE(adrsf, set_chain_address);
  201. /*
  202. * Convert n message bytes to 2*n base w=16 integers
  203. * i.e. Convert message to an array of 2*n nibbles.
  204. */
  205. slh_bytes_to_nibbles(msg, n, msg_and_csum_nibbles);
  206. /* Compute a 12 bit checksum and add it to the end */
  207. compute_checksum_nibbles(msg_and_csum_nibbles, len1, msg_and_csum_nibbles + len1);
  208. adrsf->copy(sk_adrs, adrs);
  209. adrsf->set_type_and_clear(sk_adrs, SLH_ADRS_TYPE_WOTS_PRF);
  210. adrsf->copy_keypair_address(sk_adrs, adrs);
  211. for (i = 0; i < len; ++i) {
  212. set_chain_address(sk_adrs, i);
  213. /* compute chain i secret */
  214. if (!PRF(ctx, pk_seed, sk_seed, sk_adrs, sk, sizeof(sk)))
  215. goto err;
  216. set_chain_address(adrs, i);
  217. /* compute chain i signature */
  218. if (!slh_wots_chain(ctx, sk, 0, msg_and_csum_nibbles[i],
  219. pk_seed, adrs, sig_wpkt))
  220. goto err;
  221. }
  222. ret = 1;
  223. err:
  224. return ret;
  225. }
  226. /**
  227. * @brief Compute a candidate WOTS+ public key from a message and signature
  228. * See FIPS 205 Section 5.3 Algorithm 8
  229. *
  230. * The size of the signature is len * |n| bytes (where len = 2 * |n| + 3).
  231. *
  232. * @param ctx Contains SLH_DSA algorithm functions and constants.
  233. * @param sig_rpkt A PACKET object to read a WOTS+ signature from
  234. * @param msg A message of size |n| bytes.
  235. * @param pk_seed The public key seed of size |n|.
  236. * @param adrs An ADRS object containing the layer address, tree address and
  237. * key pair address that of the WOTS+ key used to sign the message.
  238. * @param pk_out The returned public key candidate of size |n|
  239. * @param pk_out_len The maximum size of |pk_out|
  240. * @returns 1 on success, or 0 on error.
  241. */
  242. int ossl_slh_wots_pk_from_sig(SLH_DSA_HASH_CTX *ctx,
  243. PACKET *sig_rpkt, const uint8_t *msg,
  244. const uint8_t *pk_seed, uint8_t *adrs,
  245. uint8_t *pk_out, size_t pk_out_len)
  246. {
  247. int ret = 0;
  248. const SLH_DSA_KEY *key = ctx->key;
  249. uint8_t msg_and_csum_nibbles[SLH_WOTS_LEN_MAX];
  250. size_t i;
  251. size_t n = key->params->n;
  252. size_t len1 = SLH_WOTS_LEN1(n);
  253. size_t len = len1 + SLH_WOTS_LEN2; /* 2n + 3 */
  254. const uint8_t *sig_i; /* Pointer into |sig_rpkt| buffer */
  255. uint8_t tmp[SLH_WOTS_LEN_MAX * SLH_MAX_N];
  256. WPACKET pkt, *tmp_pkt = &pkt;
  257. size_t tmp_len = 0;
  258. SLH_HASH_FUNC_DECLARE(key, hashf);
  259. SLH_ADRS_FUNC_DECLARE(key, adrsf);
  260. SLH_ADRS_FN_DECLARE(adrsf, set_chain_address);
  261. SLH_ADRS_DECLARE(wots_pk_adrs);
  262. if (!WPACKET_init_static_len(tmp_pkt, tmp, sizeof(tmp), 0))
  263. return 0;
  264. slh_bytes_to_nibbles(msg, n, msg_and_csum_nibbles);
  265. compute_checksum_nibbles(msg_and_csum_nibbles, len1, msg_and_csum_nibbles + len1);
  266. /* Compute the end nodes for each of the chains */
  267. for (i = 0; i < len; ++i) {
  268. set_chain_address(adrs, i);
  269. if (!PACKET_get_bytes(sig_rpkt, &sig_i, n)
  270. || !slh_wots_chain(ctx, sig_i, msg_and_csum_nibbles[i],
  271. NIBBLE_MASK - msg_and_csum_nibbles[i],
  272. pk_seed, adrs, tmp_pkt))
  273. goto err;
  274. }
  275. /* compress the computed public key value */
  276. adrsf->copy(wots_pk_adrs, adrs);
  277. adrsf->set_type_and_clear(wots_pk_adrs, SLH_ADRS_TYPE_WOTS_PK);
  278. adrsf->copy_keypair_address(wots_pk_adrs, adrs);
  279. if (!WPACKET_get_total_written(tmp_pkt, &tmp_len))
  280. goto err;
  281. ret = hashf->T(ctx, pk_seed, wots_pk_adrs, tmp, tmp_len,
  282. pk_out, pk_out_len);
  283. err:
  284. if (!WPACKET_finish(tmp_pkt))
  285. ret = 0;
  286. return ret;
  287. }