/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 |