Botan 3.11.1
Crypto and TLS for C&
tss.cpp
Go to the documentation of this file.
1/*
2* RTSS (threshold secret sharing)
3* (C) 2009,2018,2026 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/tss.h>
9
10#include <botan/exceptn.h>
11#include <botan/hash.h>
12#include <botan/hex.h>
13#include <botan/rng.h>
14#include <botan/internal/ct_utils.h>
15#include <botan/internal/loadstor.h>
16
17namespace Botan {
18
19namespace {
20
21const size_t RTSS_HEADER_SIZE = 20;
22
23/**
24* Constant-time multiplication in GF(2^8) mod 0x11B
25*/
26uint8_t tss_gf_mul(uint8_t x, uint8_t y) {
27 uint8_t r = 0;
28 for(size_t i = 0; i != 8; ++i) {
29 r ^= CT::Mask<uint8_t>::expand(y & 1).if_set_return(x);
30 x = (x << 1) ^ CT::Mask<uint8_t>::expand_top_bit(x).if_set_return(0x1B);
31 y >>= 1;
32 }
33 return r;
34}
35
36/**
37* Inversion in GF(2^8) via Fermat's little theorem.
38*
39* Returns 0 for input 0 - a case which should not occur in our usage.
40*
41* Really this does not need to be constant-time - we only use inversion when
42* computing the Lagrange coefficients, which is derived entirely from public data.
43*/
44uint8_t tss_gf_inv(uint8_t x) {
45 const uint8_t x2 = tss_gf_mul(x, x);
46 const uint8_t x3 = tss_gf_mul(x2, x);
47 const uint8_t x6 = tss_gf_mul(x3, x3);
48 const uint8_t x12 = tss_gf_mul(x6, x6);
49 const uint8_t x15 = tss_gf_mul(x12, x3);
50 const uint8_t x30 = tss_gf_mul(x15, x15);
51 const uint8_t x60 = tss_gf_mul(x30, x30);
52 const uint8_t x120 = tss_gf_mul(x60, x60);
53 const uint8_t x126 = tss_gf_mul(x120, x6);
54 const uint8_t x127 = tss_gf_mul(x126, x);
55 return tss_gf_mul(x127, x127);
56}
57
58uint8_t rtss_hash_id(std::string_view hash_name) {
59 if(hash_name == "None") {
60 return 0;
61 } else if(hash_name == "SHA-1") {
62 return 1;
63 } else if(hash_name == "SHA-256") {
64 return 2;
65 } else {
66 throw Invalid_Argument("RTSS only supports SHA-1 and SHA-256");
67 }
68}
69
70std::unique_ptr<HashFunction> get_rtss_hash_by_id(uint8_t id) {
71 if(id == 0) {
72 return std::unique_ptr<HashFunction>();
73 }
74 if(id == 1) {
75 return HashFunction::create_or_throw("SHA-1");
76 } else if(id == 2) {
77 return HashFunction::create_or_throw("SHA-256");
78 } else {
79 throw Decoding_Error("Unknown RTSS hash identifier");
80 }
81}
82
83} // namespace
84
85RTSS_Share::RTSS_Share(std::string_view hex_input) {
86 m_contents = hex_decode_locked(hex_input);
87}
88
89RTSS_Share::RTSS_Share(const uint8_t bin[], size_t len) {
90 m_contents.assign(bin, bin + len);
91}
92
93uint8_t RTSS_Share::share_id() const {
94 if(!initialized()) {
95 throw Invalid_State("RTSS_Share::share_id not initialized");
96 }
97
98 if(m_contents.size() < RTSS_HEADER_SIZE + 1) {
99 throw Decoding_Error("RTSS_Share::share_id invalid share data");
100 }
101
102 return m_contents[20];
103}
104
105std::string RTSS_Share::to_string() const {
106 return hex_encode(m_contents.data(), m_contents.size());
107}
108
109std::vector<RTSS_Share> RTSS_Share::split(
110 uint8_t M, uint8_t N, const uint8_t S[], uint16_t S_len, const uint8_t identifier[16], RandomNumberGenerator& rng) {
111 return RTSS_Share::split(M, N, S, S_len, std::vector<uint8_t>(identifier, identifier + 16), "SHA-256", rng);
112}
113
114std::vector<RTSS_Share> RTSS_Share::split(uint8_t M,
115 uint8_t N,
116 const uint8_t S[],
117 uint16_t S_len,
118 const std::vector<uint8_t>& identifier,
119 std::string_view hash_fn,
121 if(M <= 1 || N <= 1 || M > N || N >= 255) {
122 throw Invalid_Argument("RTSS_Share::split: Invalid N or M");
123 }
124
125 if(identifier.size() > 16) {
126 throw Invalid_Argument("RTSS_Share::split Invalid identifier size");
127 }
128
129 const uint8_t hash_id = rtss_hash_id(hash_fn);
130
131 std::unique_ptr<HashFunction> hash;
132 if(hash_id > 0) {
133 hash = HashFunction::create_or_throw(hash_fn);
134 }
135
136 // secret = S || H(S)
137 secure_vector<uint8_t> secret(S, S + S_len);
138 if(hash) {
139 secret += hash->process(S, S_len);
140 }
141
142 if(secret.size() >= 0xFFFE) {
143 throw Encoding_Error("RTSS_Share::split secret too large for TSS format");
144 }
145
146 // +1 byte for the share ID
147 const uint16_t share_len = static_cast<uint16_t>(secret.size() + 1);
148
149 secure_vector<uint8_t> share_header(RTSS_HEADER_SIZE);
150 copy_mem(share_header.data(), identifier.data(), identifier.size());
151 share_header[16] = hash_id;
152 share_header[17] = M;
153 share_header[18] = get_byte<0>(share_len);
154 share_header[19] = get_byte<1>(share_len);
155
156 // Create RTSS header in each share
157 std::vector<RTSS_Share> shares(N);
158
159 for(uint8_t i = 0; i != N; ++i) {
160 shares[i].m_contents.reserve(share_header.size() + share_len);
161 shares[i].m_contents = share_header;
162 }
163
164 // Choose sequential values for X starting from 1
165 for(uint8_t i = 0; i != N; ++i) {
166 shares[i].m_contents.push_back(i + 1);
167 }
168
169 for(const uint8_t secret_byte : secret) {
170 std::vector<uint8_t> coefficients(M - 1);
171 rng.randomize(coefficients.data(), coefficients.size());
172
173 for(uint8_t j = 0; j != N; ++j) {
174 const uint8_t X = j + 1;
175
176 uint8_t sum = secret_byte;
177 uint8_t X_i = X;
178
179 for(const uint8_t cb : coefficients) {
180 sum ^= tss_gf_mul(X_i, cb);
181 X_i = tss_gf_mul(X_i, X);
182 }
183
184 shares[j].m_contents.push_back(sum);
185 }
186 }
187
188 return shares;
189}
190
191secure_vector<uint8_t> RTSS_Share::reconstruct(const std::vector<RTSS_Share>& shares) {
192 if(shares.size() <= 1) {
193 throw Decoding_Error("Insufficient shares to do TSS reconstruction");
194 }
195
196 for(size_t i = 0; i != shares.size(); ++i) {
197 if(shares[i].size() < RTSS_HEADER_SIZE + 1) {
198 throw Decoding_Error("Missing or malformed RTSS header");
199 }
200
201 if(shares[i].share_id() == 0) {
202 throw Decoding_Error("Invalid (id = 0) RTSS share detected");
203 }
204
205 if(i > 0) {
206 if(shares[i].size() != shares[0].size()) {
207 throw Decoding_Error("Different sized RTSS shares detected");
208 }
209
210 if(!CT::is_equal(shares[0].m_contents.data(), shares[i].m_contents.data(), RTSS_HEADER_SIZE).as_bool()) {
211 throw Decoding_Error("Different RTSS headers detected");
212 }
213 }
214 }
215
216 const uint8_t N = shares[0].m_contents[17];
217
218 if(shares.size() < N) {
219 throw Decoding_Error("Insufficient shares to do TSS reconstruction");
220 }
221
222 const uint16_t share_len = make_uint16(shares[0].m_contents[18], shares[0].m_contents[19]);
223
224 const uint8_t hash_id = shares[0].m_contents[16];
225 auto hash = get_rtss_hash_by_id(hash_id);
226 const size_t hash_len = (hash ? hash->output_length() : 0);
227
228 if(shares[0].size() != RTSS_HEADER_SIZE + share_len) {
229 /*
230 * This second (laxer) check accommodates a bug in TSS that was
231 * fixed in 2.9.0 - previous versions used the length of the
232 * *secret* here, instead of the length of the *share*, which is
233 * precisely 1 + hash_len longer.
234 */
235 if(shares[0].size() <= RTSS_HEADER_SIZE + 1 + hash_len) {
236 throw Decoding_Error("Bad RTSS length field in header");
237 }
238 }
239
240 std::vector<uint8_t> V(shares.size());
241 secure_vector<uint8_t> recovered;
242
243 // Compute the Lagrange coefficients
244 std::vector<uint8_t> lagrange_coeffs(shares.size());
245 for(size_t k = 0; k != shares.size(); ++k) {
246 uint8_t coeff = 1;
247 for(size_t l = 0; l != shares.size(); ++l) {
248 if(k == l) {
249 continue;
250 }
251 const uint8_t share_k = shares[k].share_id();
252 const uint8_t share_l = shares[l].share_id();
253 if(share_k == share_l) {
254 throw Decoding_Error("Duplicate shares found in RTSS recovery");
255 }
256 // We already verified this earlier in the function
257 BOTAN_ASSERT_NOMSG(share_k > 0 && share_l > 0);
258 const uint8_t div = tss_gf_mul(share_l, tss_gf_inv(share_k ^ share_l));
259 coeff = tss_gf_mul(coeff, div);
260 }
261 lagrange_coeffs[k] = coeff;
262 }
263
264 for(size_t i = RTSS_HEADER_SIZE + 1; i != shares[0].size(); ++i) {
265 for(size_t j = 0; j != V.size(); ++j) {
266 V[j] = shares[j].m_contents[i];
267 }
268
269 /*
270 * Interpolation step
271 *
272 * This is effectively a multi-scalar multiplication (aka sum-of-products)
273 * where one of the inputs, namely the Lagrange coefficients, are public.
274 * If optimizing this function further was useful, this would be the place
275 * to start, for example by using Pippeneger's algorithm.
276 */
277 uint8_t r = 0;
278 for(size_t k = 0; k != shares.size(); ++k) {
279 r ^= tss_gf_mul(V[k], lagrange_coeffs[k]);
280 }
281 recovered.push_back(r);
282 }
283
284 if(hash) {
285 if(recovered.size() < hash->output_length()) {
286 throw Decoding_Error("RTSS recovered value too short to be valid");
287 }
288
289 const size_t secret_len = recovered.size() - hash->output_length();
290
291 hash->update(recovered.data(), secret_len);
292 secure_vector<uint8_t> hash_check = hash->final();
293
294 if(!CT::is_equal(hash_check.data(), &recovered[secret_len], hash->output_length()).as_bool()) {
295 throw Decoding_Error("RTSS hash check failed");
296 }
297
298 // remove the trailing hash value
299 recovered.resize(secret_len);
300 }
301
302 return recovered;
303}
304
305} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
static constexpr Mask< T > expand(T v)
Definition ct_utils.h:392
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:308
uint8_t share_id() const
Definition tss.cpp:93
size_t size() const
Definition tss.h:92
static std::vector< RTSS_Share > split(uint8_t M, uint8_t N, const uint8_t secret[], uint16_t secret_len, const uint8_t identifier[16], RandomNumberGenerator &rng)
Definition tss.cpp:109
RTSS_Share()=default
std::string to_string() const
Definition tss.cpp:105
static secure_vector< uint8_t > reconstruct(const std::vector< RTSS_Share > &shares)
Definition tss.cpp:191
bool initialized() const
Definition tss.h:97
void randomize(std::span< uint8_t > output)
Definition rng.h:75
constexpr CT::Mask< T > is_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:798
constexpr uint8_t get_byte(T input)
Definition loadstor.h:79
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
secure_vector< uint8_t > hex_decode_locked(const char input[], size_t input_length, bool ignore_ws)
Definition hex.cpp:135
void hex_encode(char output[], const uint8_t input[], size_t input_length, bool uppercase)
Definition hex.cpp:34
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:68
constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1)
Definition loadstor.h:92