Coverage Report

Created: 2025-04-11 06:34

/src/botan/src/lib/pubkey/sphincsplus/sphincsplus_common/sp_fors.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * FORS - Forest of Random Subsets (FIPS 205, Section 8)
3
 * (C) 2023 Jack Lloyd
4
 *     2023 Fabian Albert, René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity
5
 *
6
 * Parts of this file have been adapted from https://github.com/sphincs/sphincsplus
7
 *
8
 * Botan is released under the Simplified BSD License (see license.txt)
9
 */
10
11
#include <botan/internal/sp_fors.h>
12
13
#include <botan/assert.h>
14
#include <botan/hash.h>
15
#include <botan/sp_parameters.h>
16
17
#include <botan/internal/sp_address.h>
18
#include <botan/internal/sp_hash.h>
19
#include <botan/internal/sp_treehash.h>
20
#include <botan/internal/sp_types.h>
21
#include <botan/internal/stl_util.h>
22
23
namespace Botan {
24
25
namespace {
26
27
/// FIPS 205, Algorithm 4: base_2^b(X,b,out_len) with b = a and out_len = k (for usage in FORS)
28
0
std::vector<TreeNodeIndex> fors_message_to_indices(std::span<const uint8_t> message, const Sphincs_Parameters& params) {
29
0
   BOTAN_ASSERT_NOMSG((message.size() * 8) >= (params.k() * params.a()));
30
31
0
   std::vector<TreeNodeIndex> indices(params.k());
32
33
0
   uint32_t offset = 0;
34
35
   // This is one of the few places where the logic of SPHINCS+ round 3.1 and SLH-DSA differs
36
0
   auto update_idx = [&]() -> std::function<void(TreeNodeIndex&, uint32_t)> {
37
0
#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
38
0
      if(params.is_slh_dsa()) {
39
0
         return [&](TreeNodeIndex& idx, uint32_t i) {
40
0
            idx ^= (((message[offset >> 3] >> (~offset & 0x7)) & 0x1) << (params.a() - 1 - i));
41
0
         };
42
0
      }
43
0
#endif
44
0
#if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE)
45
0
      if(!params.is_slh_dsa()) {
46
0
         return [&](TreeNodeIndex& idx, uint32_t i) { idx ^= (((message[offset >> 3] >> (offset & 0x7)) & 0x1) << i); };
47
0
      }
48
0
#endif
49
0
      throw Internal_Error("Missing FORS index update logic for SPHINCS+ or SLH-DSA");
50
0
   }();
51
52
0
   for(auto& idx : indices) {
53
0
      for(uint32_t i = 0; i < params.a(); ++i, ++offset) {
54
0
         update_idx(idx, i);
55
0
      }
56
0
   }
57
58
0
   return indices;
59
0
}
60
61
}  // namespace
62
63
SphincsTreeNode fors_sign_and_pkgen(StrongSpan<ForsSignature> sig_out,
64
                                    const SphincsHashedMessage& hashed_message,
65
                                    const SphincsSecretSeed& secret_seed,
66
                                    const Sphincs_Address& address,
67
                                    const Sphincs_Parameters& params,
68
0
                                    Sphincs_Hash_Functions& hashes) {
69
0
   BOTAN_ASSERT_NOMSG(sig_out.size() == params.fors_signature_bytes());
70
71
0
   const auto indices = fors_message_to_indices(hashed_message, params);
72
73
0
   auto fors_tree_addr = Sphincs_Address::as_keypair_from(address);
74
75
0
   auto fors_pk_addr = Sphincs_Address::as_keypair_from(address).set_type(Sphincs_Address::ForsTreeRootsCompression);
76
77
0
   std::vector<uint8_t> roots_buffer(params.k() * params.n());
78
0
   BufferStuffer roots(roots_buffer);
79
0
   BufferStuffer sig(sig_out);
80
81
   // Buffer to hold the FORS leafs during tree traversal
82
   // (Avoids a secure_vector allocation/deallocation in the hot path)
83
0
   ForsLeafSecret fors_leaf_secret(params.n());
84
85
   // For each of the k FORS subtrees: Compute the secret leaf, the authentication path
86
   // and the trees' root and append the signature respectively
87
0
   BOTAN_ASSERT_NOMSG(indices.size() == params.k());
88
0
   for(uint32_t i = 0; i < params.k(); ++i) {
89
0
      uint32_t idx_offset = i * (1 << params.a());
90
91
      // Compute the secret leaf given by the chunk of the message and append it to the signature
92
0
      fors_tree_addr.set_type(Sphincs_Address_Type::ForsKeyGeneration)
93
0
         .set_tree_height(TreeLayerIndex(0))
94
0
         .set_tree_index(indices[i] + idx_offset);
95
96
0
      hashes.PRF(sig.next<ForsLeafSecret>(params.n()), secret_seed, fors_tree_addr);
97
98
      // Compute the authentication path and root for this leaf node
99
0
      fors_tree_addr.set_type(Sphincs_Address_Type::ForsTree);
100
101
0
      GenerateLeafFunction fors_gen_leaf = [&](StrongSpan<SphincsTreeNode> out_root, TreeNodeIndex address_index) {
102
0
         fors_tree_addr.set_tree_index(address_index);
103
0
         fors_tree_addr.set_type(Sphincs_Address_Type::ForsKeyGeneration);
104
105
0
         hashes.PRF(fors_leaf_secret, secret_seed, fors_tree_addr);
106
107
0
         fors_tree_addr.set_type(Sphincs_Address_Type::ForsTree);
108
0
         hashes.T(out_root, fors_tree_addr, fors_leaf_secret);
109
0
      };
110
111
0
      treehash(roots.next<SphincsTreeNode>(params.n()),
112
0
               sig.next<SphincsAuthenticationPath>(params.a() * params.n()),
113
0
               params,
114
0
               hashes,
115
0
               indices[i],
116
0
               idx_offset,
117
0
               params.a(),
118
0
               fors_gen_leaf,
119
0
               fors_tree_addr);
120
0
   }
121
122
0
   BOTAN_ASSERT_NOMSG(sig.full());
123
0
   BOTAN_ASSERT_NOMSG(roots.full());
124
125
   // Compute the public key by the hash of the concatenation of all roots
126
0
   return hashes.T<SphincsTreeNode>(fors_pk_addr, roots_buffer);
127
0
}
128
129
SphincsTreeNode fors_public_key_from_signature(const SphincsHashedMessage& hashed_message,
130
                                               StrongSpan<const ForsSignature> signature,
131
                                               const Sphincs_Address& address,
132
                                               const Sphincs_Parameters& params,
133
0
                                               Sphincs_Hash_Functions& hashes) {
134
0
   const auto indices = fors_message_to_indices(hashed_message, params);
135
136
0
   auto fors_tree_addr = Sphincs_Address::as_keypair_from(address).set_type(Sphincs_Address::ForsTree);
137
138
0
   auto fors_pk_addr = Sphincs_Address::as_keypair_from(address).set_type(Sphincs_Address::ForsTreeRootsCompression);
139
140
0
   BufferSlicer s(signature);
141
0
   std::vector<uint8_t> roots_buffer(params.k() * params.n());
142
0
   BufferStuffer roots(roots_buffer);
143
144
   // For each of the k FORS subtrees: Reconstruct the subtree's root node by using the
145
   // leaf and the authentication path offered in the FORS signature.
146
0
   BOTAN_ASSERT_NOMSG(indices.size() == params.k());
147
0
   for(uint32_t i = 0; i < params.k(); ++i) {
148
0
      uint32_t idx_offset = i * (1 << params.a());
149
150
      // Compute the FORS leaf by using the secret leaf contained in the signature
151
0
      fors_tree_addr.set_tree_height(TreeLayerIndex(0)).set_tree_index(indices[i] + idx_offset);
152
0
      auto fors_leaf_secret = s.take<ForsLeafSecret>(params.n());
153
0
      auto auth_path = s.take<SphincsAuthenticationPath>(params.n() * params.a());
154
0
      auto leaf = hashes.T<SphincsTreeNode>(fors_tree_addr, fors_leaf_secret);
155
156
      // Reconstruct the subtree's root using the authentication path
157
0
      compute_root(roots.next<SphincsTreeNode>(params.n()),
158
0
                   params,
159
0
                   hashes,
160
0
                   leaf,
161
0
                   indices[i],
162
0
                   idx_offset,
163
0
                   auth_path,
164
0
                   params.a(),
165
0
                   fors_tree_addr);
166
0
   }
167
168
0
   BOTAN_ASSERT_NOMSG(roots.full());
169
170
   // Reconstruct the public key the signature creates with the hash of the concatenation of all roots
171
   // Only if the signature is valid, the pk is the correct FORS pk.
172
0
   return hashes.T<SphincsTreeNode>(fors_pk_addr, roots_buffer);
173
0
}
174
175
}  // namespace Botan