• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

libbitcoin / libbitcoin-system / 13069453084

31 Jan 2025 08:55AM UTC coverage: 82.728% (-0.007%) from 82.735%
13069453084

push

github

web-flow
Merge pull request #1604 from evoskuil/master

Set _CRTDBG_MAP_ALLOC for vc++ debug mode leak tracking.

2 of 2 new or added lines in 1 file covered. (100.0%)

15 existing lines in 11 files now uncovered.

10039 of 12135 relevant lines covered (82.73%)

3871639.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.19
/src/crypto/ring_signature.cpp
1
/**
2
 * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
3
 *
4
 * This file is part of libbitcoin.
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
#include <bitcoin/system/crypto/ring_signature.hpp>
20

21
#include <algorithm>
22
#include <map>
23
#include <numeric>
24
#include <vector>
25
#include <secp256k1.h>
26
#include <bitcoin/system/hash/hash.hpp>
27
#include <bitcoin/system/stream/stream.hpp>
28
#include <bitcoin/system/wallet/keys/hd_private.hpp>
29
#include <bitcoin/system/wallet/keys/ec_point.hpp>
30
#include <bitcoin/system/wallet/keys/ec_scalar.hpp>
31

32
namespace libbitcoin {
33
namespace system {
34

35
typedef std::vector<uint32_t> index_list;
36
typedef std::map<ec_compressed, ec_secret> secret_keys_map;
37

38
static ec_scalar borromean_hash(const hash_digest& M, const data_slice& R,
116✔
39
    uint32_t i, uint32_t j) NOEXCEPT
40
{
41
    // e = H(M || R || i || j)
42
    hash_digest hash;
116✔
43
    hash::sha256::copy sink(hash);
116✔
44
    sink.write_bytes(R);
116✔
45
    sink.write_bytes(M);
116✔
46
    sink.write_4_bytes_big_endian(i);
116✔
47
    sink.write_4_bytes_big_endian(j);
116✔
48
    sink.flush();
116✔
49
    return hash;
232✔
50
}
116✔
51

52
// Take a list of secret keys and generate a mapping from public key -> secret.
53
static bool generate_keys_map(secret_keys_map& out,
2✔
54
    const secret_list& secrets) NOEXCEPT
55
{
56
    for (const auto& secret: secrets)
8✔
57
    {
58
        ec_compressed public_key;
6✔
59
        if (!secret_to_public(public_key, secret))
6✔
60
            return false;
×
61

62
        out.insert({ public_key, secret });
12✔
63
    }
64

65
    return true;
66
}
67

68
// Make a list of public keys for which we have the corresponding secret key
69
// in a single ring of public keys.
70
static const compressed_list known_keys_in_ring(
6✔
71
    const secret_keys_map& secret_keys, const compressed_list& ring) NOEXCEPT
72
{
73
    compressed_list known_ring;
6✔
74
    known_ring.reserve(ring.size());
6✔
75

76
    for (const auto& key: ring)
28✔
77
        if (secret_keys.find(key) != secret_keys.end())
22✔
78
            known_ring.push_back(key);
6✔
79

80
    known_ring.shrink_to_fit();
6✔
81
    return known_ring;
6✔
82
}
83

84
// For all rings, make a list of known public keys corresponding to each ring.
85
static key_rings partition_keys_into_rings(const secret_keys_map& secret_keys,
2✔
86
    const key_rings& rings) NOEXCEPT
87
{
88
    key_rings known_keys;
2✔
89
    known_keys.reserve(rings.size());
2✔
90

91
    for (const auto& ring: rings)
8✔
92
        known_keys.push_back(known_keys_in_ring(secret_keys, ring));
12✔
93

94
    return known_keys;
2✔
UNCOV
95
}
×
96

97
// Make a list of indexes of where our known key occurs in each ring of
98
// public keys. That is given a ring of {A, B, C} where we know the
99
// private key of B, it will return 1 (the index in the ring).
100
// This function computes this for all rings.
101
static bool create_key_indexes(index_list& out, const key_rings& rings,
2✔
102
    const key_rings& known_keys_by_ring) NOEXCEPT
103
{
104
    BC_ASSERT(known_keys_by_ring.size() == rings.size());
2✔
105

106
    for (uint32_t i = 0; i < rings.size(); ++i)
8✔
107
    {
108
        const auto& ring = rings[i];
6✔
109
        const auto& known = known_keys_by_ring[i];
6✔
110
        const auto it = std::find(ring.begin(), ring.end(), known.back());
6✔
111

112
        if (it == ring.end())
6✔
113
            return false;
×
114

115
        // The cast is safe because indexes cannot exceed max_uint32.
116
        out.push_back(static_cast<uint32_t>(std::distance(ring.begin(), it)));
6✔
117
    }
118

119
    return true;
120
}
121

122
static bool generate_known_indexes(index_list& out, const key_rings& rings,
2✔
123
    const secret_keys_map& secret_keys) NOEXCEPT
124
{
125
    auto known_keys_by_ring = partition_keys_into_rings(secret_keys, rings);
2✔
126
    BC_ASSERT(known_keys_by_ring.size() == rings.size());
2✔
127

128
    const auto empty = [](const compressed_list& ring) NOEXCEPT
8✔
129
    {
130
        return ring.empty();
6✔
131
    };
132

133
    // Check there is a known secret key in each ring.
134
    const auto has_empty = std::any_of(known_keys_by_ring.begin(),
2✔
135
        known_keys_by_ring.end(), empty);
136

137
    // Compute indexes for known keys inside the rings.
138
    return !has_empty && create_key_indexes(out, rings, known_keys_by_ring);
4✔
139
}
2✔
140

141
static ec_point calculate_R(const ec_scalar& s, const ec_scalar& e,
93✔
142
    const ec_point& P) NOEXCEPT
143
{
144
    return s * ec_point::generator + e * P;
93✔
145
}
146

147
static ec_point calculate_last_R_signing(const compressed_list& ring,
6✔
148
    uint32_t i, const hash_digest& digest, const ring_signature& signature,
149
    const uint32_t known_key_index, const secret_list& salts) NOEXCEPT
150
{
151
    auto R_i_j = salts[i] * ec_point::generator;
6✔
152
    if (!R_i_j)
6✔
153
        return {};
×
154

155
    // Start one above index of known key and loop until the end.
156
    for (uint32_t j = add1(known_key_index); j < ring.size(); ++j)
20✔
157
    {
158
        BC_ASSERT(j < signature.proofs[i].size());
14✔
159
        const ec_scalar s(signature.proofs[i][j]);
14✔
160
        if (!s)
14✔
161
            return {};
×
162

163
        // Calculate e and R until the end of this ring.
164
        const auto e_i_j = borromean_hash(digest, R_i_j.point(), i, j);
14✔
165
        if (!e_i_j)
14✔
166
            return {};
×
167

168
        R_i_j = calculate_R(s, e_i_j, ring[j]);
14✔
169
        if (!R_i_j)
14✔
170
            return {};
×
171
    }
172

173
    return R_i_j;
6✔
174
}
175

176
static bool calculate_e0(ring_signature& out, const key_rings& rings,
2✔
177
    const hash_digest& digest, const secret_list& salts,
178
    const index_list& known_key_indexes) NOEXCEPT
179
{
180
    hash_digest hash;
2✔
181
    hash::sha256::copy sink(hash);
2✔
182

183
    BC_ASSERT(known_key_indexes.size() == rings.size());
2✔
184
    BC_ASSERT(out.proofs.size() == rings.size());
2✔
185
    BC_ASSERT(salts.size() == rings.size());
2✔
186

187
    for (uint32_t i = 0; i < rings.size(); ++i)
8✔
188
    {
189
        const auto& ring = rings[i];
6✔
190
        const auto known_key_index = known_key_indexes[i];
6✔
191

192
        // Calculate the last R value.
193
        const auto last_R = calculate_last_R_signing(ring, i, digest, out,
6✔
194
            known_key_index, salts);
195

196
        if (!last_R)
6✔
197
            return false;
×
198

199
        // Add this ring to e0
200
        sink.write_bytes(last_R.point());
6✔
201
    }
202

203
    sink.write_bytes(digest);
2✔
204
    sink.flush();
2✔
205

206
    out.challenge = hash;
2✔
207
    return true;
2✔
208
}
2✔
209

210
static bool calculate_e_at_known_key_index(ec_scalar& e_i_j,
6✔
211
    const ring_signature& signature, const compressed_list& ring,
212
    const hash_digest& digest, const uint32_t i,
213
    const uint32_t known_key_index) NOEXCEPT
214
{
215
    BC_ASSERT(signature.proofs[i].size() > known_key_index);
6✔
216
    BC_ASSERT(ring.size() > known_key_index);
6✔
217

218
    // Loop until index of known key.
219
    for (uint32_t j = 0; j < known_key_index; ++j)
8✔
220
    {
221
        const ec_scalar s{ signature.proofs[i][j] };
2✔
222
        if (!s)
2✔
223
            return false;
×
224

225
        // Calculate e and R until reaching the index.
226
        const auto R = calculate_R(s, e_i_j, ring[j]);
2✔
227
        if (!R)
2✔
228
            return false;
229

230
        e_i_j = borromean_hash(digest, R.point(), i, add1(j));
2✔
231
        if (!e_i_j)
2✔
232
            return false;
233
    }
234

235
    return true;
236
}
237

238
static bool join_rings(ring_signature& out, const key_rings& rings,
2✔
239
    const hash_digest& digest, const secret_list& salts,
240
    const index_list& known_key_indexes, secret_keys_map& secret_keys) NOEXCEPT
241
{
242
    for (uint32_t i = 0; i < rings.size(); ++i)
8✔
243
    {
244
        const auto& ring = rings[i];
6✔
245
        const auto known_key_index = known_key_indexes[i];
6✔
246

247
        // Calculate starting e value of this current ring.
248
        auto e_i_j = borromean_hash(digest, out.challenge, i, zero);
6✔
249
        if (!e_i_j)
6✔
250
            return false;
×
251

252
        if (!calculate_e_at_known_key_index(e_i_j, out, ring, digest, i,
6✔
253
            known_key_index))
254
            return false;
255

256
        // Find secret key used for calculation in the next step.
257
        BC_ASSERT(known_key_index < ring.size());
6✔
258
        const auto& known_public_key = ring[known_key_index];
6✔
259

260
        BC_ASSERT(secret_keys.find(known_public_key) != secret_keys.end());
6✔
261
        const auto& secret = secret_keys[known_public_key];
6✔
262

263
        // Now close the ring using this calculation:
264
        const auto& k = salts[i];
6✔
265
        const auto& x = secret;
6✔
266

267
        const auto s = k - e_i_j * x;
6✔
268
        if (!s)
6✔
269
            return false;
270

271
        // Close the ring.
272
        out.proofs[i][known_key_index] = s;
6✔
273
    }
274

275
    return true;
276
}
277

278
static ec_point calculate_last_R_verify(const compressed_list& ring,
17✔
279
    ec_scalar e_i_j, uint32_t i, const hash_digest& digest,
280
    const ring_signature& signature) NOEXCEPT
281
{
282
    ec_point R_i_j;
17✔
283
    BC_ASSERT(signature.proofs[i].size() == ring.size());
284

285
    for (uint32_t j = 0; j < ring.size(); ++j)
94✔
286
    {
287
        // s_i_j
288
        const ec_scalar s(signature.proofs[i][j]);
77✔
289

290
        if (!s || !e_i_j)
77✔
291
            return {};
×
292

293
        // Calculate R and e values until the end.
294
        R_i_j = calculate_R(s, e_i_j, ring[j]);
77✔
295
        if (!R_i_j)
77✔
296
            return {};
×
297

298
        // Calculate the next e value.
299
        e_i_j = borromean_hash(digest, R_i_j.point(), i, j + 1u);
77✔
300
        if (!e_i_j)
77✔
301
            return {};
×
302
    }
303

304
    return R_i_j;
17✔
305
}
306

307
// API
308
// ----------------------------------------------------------------------------
309

310
hash_digest digest(const data_slice& message, const key_rings& rings) NOEXCEPT
1✔
311
{
312
    hash_digest hash;
1✔
313
    hash::sha256::copy sink(hash);
1✔
314

315
    sink.write_bytes(message);
1✔
316

317
    for (const auto& ring: rings)
4✔
318
        for (const auto& key: ring)
14✔
319
            sink.write_bytes(key);
11✔
320

321
    sink.flush();
1✔
322
    return hash;
1✔
323
}
1✔
324

325
bool sign(ring_signature& out, const secret_list& secrets,
2✔
326
    const key_rings& rings, const hash_digest& digest,
327
    const secret_list& salts) NOEXCEPT
328
{
329
    // Guard against overflow.
330
    if (rings.size() >= max_uint32)
2✔
331
        return false;
332

333
    secret_keys_map secret_keys;
2✔
334
    if (!generate_keys_map(secret_keys, secrets))
2✔
335
        return false;
336

337
    index_list known_key_indexes;
2✔
338
    if (!generate_known_indexes(known_key_indexes, rings, secret_keys))
2✔
339
        return false;
340

341
    if (!calculate_e0(out, rings, digest, salts, known_key_indexes))
2✔
342
        return false;
343

344
    // Join each ring at the index where the secret key is known.
345
    return join_rings(out, rings, digest, salts, known_key_indexes,
2✔
346
        secret_keys);
347
}
2✔
348

349
// As compared with signing, we only have to perform a single step.
350
// The ring has already been computed, so now we just need to verify
351
// it by calculating e0, and looping all the way until the end R value
352
// which we use to recalculate e0.
353
// If the values match then we have a valid ring signature.
354
bool verify(const key_rings& rings, const hash_digest& digest,
3✔
355
    const ring_signature& signature) NOEXCEPT
356
{
357
    // Guard against overflow.
358
    if (rings.size() >= max_uint32 || signature.proofs.size() != rings.size())
3✔
359
        return false;
360

361
    // Hash data to produce e0 value.
362
    hash_digest hash;
3✔
363
    hash::sha256::copy sink(hash);
3✔
364

365
    for (uint32_t i = 0; i < rings.size(); ++i)
20✔
366
    {
367
        // Calculate first e value for this ring.
368
        const auto e_i_0 = borromean_hash(digest, signature.challenge, i, zero);
17✔
369

370
        if (!e_i_0)
17✔
371
            return false;
×
372

373
        // Calculate the last R value.
374
        const auto last_R = calculate_last_R_verify(rings[i], e_i_0, i, digest,
17✔
375
            signature);
376

377
        if (!last_R)
17✔
378
            return false;
379

380
        // Add this ring to e0.
381
        sink.write_bytes(last_R.point());
17✔
382
    }
383

384
    sink.write_bytes(digest);
3✔
385
    sink.flush();
3✔
386

387
    // Verification step.
388
    return hash == signature.challenge;
3✔
389
}
3✔
390

391
} // namespace system
392
} // namespace libbitcoin
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc