Skip to content

Commit e5166ac

Browse files
committed
more tests for slate exchange round
1 parent b83eca1 commit e5166ac

File tree

5 files changed

+361
-170
lines changed

5 files changed

+361
-170
lines changed

Diff for: secp256k1-zkp/include/secp256k1_surjectionproof.h

+62-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ extern "C" {
1111
/** Maximum number of inputs that may be given in a surjection proof */
1212
#define SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS 256
1313

14+
/** Maximum number of inputs that may be used in a surjection proof */
15+
#define SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS 256
16+
1417
/** Number of bytes a serialized surjection proof requires given the
1518
* number of inputs and the number of used inputs.
1619
*/
@@ -19,7 +22,7 @@ extern "C" {
1922

2023
/** Maximum number of bytes a serialized surjection proof requires. */
2124
#define SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES_MAX \
22-
SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS)
25+
SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS)
2326

2427
/** Opaque data structure that holds a parsed surjection proof
2528
*
@@ -46,9 +49,10 @@ typedef struct {
4649
/** Bitmap of which input tags are used in the surjection proof */
4750
unsigned char used_inputs[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS / 8];
4851
/** Borromean signature: e0, scalars */
49-
unsigned char data[32 * (1 + SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS)];
52+
unsigned char data[32 * (1 + SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS)];
5053
} secp256k1_surjectionproof;
5154

55+
#ifndef USE_REDUCED_SURJECTION_PROOF_SIZE
5256
/** Parse a surjection proof
5357
*
5458
* Returns: 1 when the proof could be parsed, 0 otherwise.
@@ -70,6 +74,7 @@ SECP256K1_API int secp256k1_surjectionproof_parse(
7074
const unsigned char *input,
7175
size_t inputlen
7276
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
77+
#endif
7378

7479
/** Serialize a surjection proof
7580
*
@@ -134,6 +139,7 @@ SECP256K1_API size_t secp256k1_surjectionproof_serialized_size(
134139
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
135140

136141
/** Surjection proof initialization function; decides on inputs to use
142+
* To be used to initialize stack-allocated secp256k1_surjectionproof struct
137143
* Returns 0: inputs could not be selected
138144
* n: inputs were selected after n iterations of random selection
139145
*
@@ -142,9 +148,14 @@ SECP256K1_API size_t secp256k1_surjectionproof_serialized_size(
142148
* e.g. in a coinjoin with others' inputs, an ephemeral tag can be given;
143149
* this won't match the output tag but might be used in the anonymity set.)
144150
* n_input_tags: the number of entries in the fixed_input_tags array
145-
* n_input_tags_to_use: the number of inputs to select randomly to put in the anonymity set
151+
* n_input_tags_to_use: the number of inputs to select randomly to put in the anonymity set
152+
* Must be <= SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS
146153
* fixed_output_tag: fixed output tag
147-
* max_n_iterations: the maximum number of iterations to do before giving up
154+
* max_n_iterations: the maximum number of iterations to do before giving up. Because the
155+
* maximum number of inputs (SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS) is
156+
* limited to 256 the probability of giving up is smaller than
157+
* (255/256)^(n_input_tags_to_use*max_n_iterations).
158+
*
148159
* random_seed32: a random seed to be used for input selection
149160
* Out: proof: The proof whose bitvector will be initialized. In case of failure,
150161
* the state of the proof is undefined.
@@ -162,6 +173,51 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_initial
162173
const unsigned char *random_seed32
163174
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7);
164175

176+
177+
/** Surjection proof allocation and initialization function; decides on inputs to use
178+
* Returns 0: inputs could not be selected, or malloc failure
179+
* n: inputs were selected after n iterations of random selection
180+
*
181+
* In: ctx: pointer to a context object
182+
* proof_out_p: a pointer to a pointer to `secp256k1_surjectionproof*`.
183+
* the newly-allocated struct pointer will be saved here.
184+
* fixed_input_tags: fixed input tags `A_i` for all inputs. (If the fixed tag is not known,
185+
* e.g. in a coinjoin with others' inputs, an ephemeral tag can be given;
186+
* this won't match the output tag but might be used in the anonymity set.)
187+
* n_input_tags: the number of entries in the fixed_input_tags array
188+
* n_input_tags_to_use: the number of inputs to select randomly to put in the anonymity set
189+
* fixed_output_tag: fixed output tag
190+
* max_n_iterations: the maximum number of iterations to do before giving up. Because the
191+
* maximum number of inputs (SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS) is
192+
* limited to 256 the probability of giving up is smaller than
193+
* (255/256)^(n_input_tags_to_use*max_n_iterations).
194+
*
195+
* random_seed32: a random seed to be used for input selection
196+
* Out: proof_out_p: The pointer to newly-allocated proof whose bitvector will be initialized.
197+
* In case of failure, the pointer will be NULL.
198+
* input_index: The index of the actual input that is secretly mapped to the output
199+
*/
200+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_allocate_initialized(
201+
const secp256k1_context* ctx,
202+
secp256k1_surjectionproof** proof_out_p,
203+
size_t *input_index,
204+
const secp256k1_fixed_asset_tag* fixed_input_tags,
205+
const size_t n_input_tags,
206+
const size_t n_input_tags_to_use,
207+
const secp256k1_fixed_asset_tag* fixed_output_tag,
208+
const size_t n_max_iterations,
209+
const unsigned char *random_seed32
210+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7);
211+
212+
/** Surjection proof destroy function
213+
* deallocates the struct that was allocated with secp256k1_surjectionproof_allocate_initialized
214+
*
215+
* In: proof: pointer to secp256k1_surjectionproof struct
216+
*/
217+
SECP256K1_API void secp256k1_surjectionproof_destroy(
218+
secp256k1_surjectionproof* proof
219+
) SECP256K1_ARG_NONNULL(1);
220+
165221
/** Surjection proof generation function
166222
* Returns 0: proof could not be created
167223
* 1: proof was successfully created
@@ -187,6 +243,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_generat
187243
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
188244

189245

246+
#ifndef USE_REDUCED_SURJECTION_PROOF_SIZE
190247
/** Surjection proof verification function
191248
* Returns 0: proof was invalid
192249
* 1: proof was valid
@@ -204,6 +261,7 @@ SECP256K1_API int secp256k1_surjectionproof_verify(
204261
size_t n_ephemeral_input_tags,
205262
const secp256k1_generator* ephemeral_output_tag
206263
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
264+
#endif
207265

208266
#ifdef __cplusplus
209267
}

Diff for: secp256k1-zkp/src/modules/surjection/main_impl.h

+81-21
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@
99
#include <assert.h>
1010
#include <string.h>
1111

12+
#if defined HAVE_CONFIG_H
13+
#include "libsecp256k1-config.h"
14+
#endif
15+
16+
#include "include/secp256k1_rangeproof.h"
17+
#include "include/secp256k1_surjectionproof.h"
1218
#include "modules/rangeproof/borromean.h"
1319
#include "modules/surjection/surjection_impl.h"
1420
#include "hash.h"
15-
#include "include/secp256k1_rangeproof.h"
16-
#include "include/secp256k1_surjectionproof.h"
21+
22+
#ifdef USE_REDUCED_SURJECTION_PROOF_SIZE
23+
#undef SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS
24+
#define SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS 16
25+
#endif
1726

1827
static size_t secp256k1_count_bits_set(const unsigned char* data, size_t count) {
1928
size_t ret = 0;
@@ -35,6 +44,9 @@ static size_t secp256k1_count_bits_set(const unsigned char* data, size_t count)
3544
return ret;
3645
}
3746

47+
#ifdef USE_REDUCED_SURJECTION_PROOF_SIZE
48+
static
49+
#endif
3850
int secp256k1_surjectionproof_parse(const secp256k1_context* ctx, secp256k1_surjectionproof *proof, const unsigned char *input, size_t inputlen) {
3951
size_t n_inputs;
4052
size_t signature_len;
@@ -55,6 +67,15 @@ int secp256k1_surjectionproof_parse(const secp256k1_context* ctx, secp256k1_surj
5567
return 0;
5668
}
5769

70+
/* Check that the bitvector of used inputs is of the claimed
71+
* length; i.e. the final byte has no "padding bits" set */
72+
if (n_inputs % 8 != 0) {
73+
const unsigned char padding_mask = (~0U) << (n_inputs % 8);
74+
if ((input[2 + (n_inputs + 7) / 8 - 1] & padding_mask) != 0) {
75+
return 0;
76+
}
77+
}
78+
5879
signature_len = 32 * (1 + secp256k1_count_bits_set(&input[2], (n_inputs + 7) / 8));
5980
if (inputlen != 2 + (n_inputs + 7) / 8 + signature_len) {
6081
return 0;
@@ -151,6 +172,48 @@ static size_t secp256k1_surjectionproof_csprng_next(secp256k1_surjectionproof_cs
151172
}
152173
}
153174

175+
/* While '_allocate_initialized' may be a wordy suffix for this function, and '_create'
176+
* may have been more appropriate, '_create' could be confused with '_generate',
177+
* as the meanings for the words are close. Therefore, more wordy, but less
178+
* ambiguous suffix was chosen. */
179+
int secp256k1_surjectionproof_allocate_initialized(const secp256k1_context* ctx, secp256k1_surjectionproof** proof_out_p, size_t *input_index, const secp256k1_fixed_asset_tag* fixed_input_tags, const size_t n_input_tags, const size_t n_input_tags_to_use, const secp256k1_fixed_asset_tag* fixed_output_tag, const size_t n_max_iterations, const unsigned char *random_seed32) {
180+
int ret = 0;
181+
secp256k1_surjectionproof* proof;
182+
183+
VERIFY_CHECK(ctx != NULL);
184+
185+
ARG_CHECK(proof_out_p != NULL);
186+
*proof_out_p = 0;
187+
188+
proof = (secp256k1_surjectionproof*)checked_malloc(&ctx->error_callback, sizeof(secp256k1_surjectionproof));
189+
if (proof != NULL) {
190+
ret = secp256k1_surjectionproof_initialize(ctx, proof, input_index, fixed_input_tags, n_input_tags, n_input_tags_to_use, fixed_output_tag, n_max_iterations, random_seed32);
191+
if (ret) {
192+
*proof_out_p = proof;
193+
}
194+
else {
195+
free(proof);
196+
}
197+
}
198+
return ret;
199+
}
200+
201+
/* secp256k1_surjectionproof structure may also be allocated on the stack,
202+
* and initialized explicitly via secp256k1_surjectionproof_initialize().
203+
* Supplying stack-allocated struct to _destroy() will result in calling
204+
* free() with the pointer that points at the stack, with disasterous
205+
* consequences. Thus, it is not advised to mix heap- and stack-allocating
206+
* approaches to working with this struct. It is possible to detect this
207+
* situation by using additional field in the struct that can be set to
208+
* special value depending on the allocation path, and check it here.
209+
* But currently, it is not seen as big enough concern to warrant this extra code .*/
210+
void secp256k1_surjectionproof_destroy(secp256k1_surjectionproof* proof) {
211+
if (proof != NULL) {
212+
VERIFY_CHECK(proof->n_inputs <= SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS);
213+
free(proof);
214+
}
215+
}
216+
154217
int secp256k1_surjectionproof_initialize(const secp256k1_context* ctx, secp256k1_surjectionproof* proof, size_t *input_index, const secp256k1_fixed_asset_tag* fixed_input_tags, const size_t n_input_tags, const size_t n_input_tags_to_use, const secp256k1_fixed_asset_tag* fixed_output_tag, const size_t n_max_iterations, const unsigned char *random_seed32) {
155218
secp256k1_surjectionproof_csprng csprng;
156219
size_t n_iterations = 0;
@@ -162,6 +225,7 @@ int secp256k1_surjectionproof_initialize(const secp256k1_context* ctx, secp256k1
162225
ARG_CHECK(fixed_output_tag != NULL);
163226
ARG_CHECK(random_seed32 != NULL);
164227
ARG_CHECK(n_input_tags <= SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS);
228+
ARG_CHECK(n_input_tags_to_use <= SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS);
165229
ARG_CHECK(n_input_tags_to_use <= n_input_tags);
166230
(void) ctx;
167231

@@ -219,10 +283,8 @@ int secp256k1_surjectionproof_generate(const secp256k1_context* ctx, secp256k1_s
219283
size_t n_total_pubkeys;
220284
size_t n_used_pubkeys;
221285
size_t ring_input_index = 0;
222-
secp256k1_gej ring_pubkeys[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
223-
secp256k1_scalar borromean_s[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
224-
secp256k1_ge inputs[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
225-
secp256k1_ge output;
286+
secp256k1_gej ring_pubkeys[SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS];
287+
secp256k1_scalar borromean_s[SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS];
226288
unsigned char msg32[32];
227289

228290
VERIFY_CHECK(ctx != NULL);
@@ -261,17 +323,14 @@ int secp256k1_surjectionproof_generate(const secp256k1_context* ctx, secp256k1_s
261323
return 0;
262324
}
263325

264-
secp256k1_generator_load(&output, ephemeral_output_tag);
265-
for (i = 0; i < n_total_pubkeys; i++) {
266-
secp256k1_generator_load(&inputs[i], &ephemeral_input_tags[i]);
326+
if (secp256k1_surjection_compute_public_keys(ring_pubkeys, n_used_pubkeys, ephemeral_input_tags, n_total_pubkeys, proof->used_inputs, ephemeral_output_tag, input_index, &ring_input_index) == 0) {
327+
return 0;
267328
}
268329

269-
secp256k1_surjection_compute_public_keys(ring_pubkeys, n_used_pubkeys, inputs, n_total_pubkeys, proof->used_inputs, &output, input_index, &ring_input_index);
270-
271330
/* Produce signature */
272331
rsizes[0] = (int) n_used_pubkeys;
273332
indices[0] = (int) ring_input_index;
274-
secp256k1_surjection_genmessage(msg32, inputs, n_total_pubkeys, &output);
333+
secp256k1_surjection_genmessage(msg32, ephemeral_input_tags, n_total_pubkeys, ephemeral_output_tag);
275334
if (secp256k1_surjection_genrand(borromean_s, n_used_pubkeys, &blinding_key) == 0) {
276335
return 0;
277336
}
@@ -289,15 +348,16 @@ int secp256k1_surjectionproof_generate(const secp256k1_context* ctx, secp256k1_s
289348
return 1;
290349
}
291350

351+
#ifdef USE_REDUCED_SURJECTION_PROOF_SIZE
352+
static
353+
#endif
292354
int secp256k1_surjectionproof_verify(const secp256k1_context* ctx, const secp256k1_surjectionproof* proof, const secp256k1_generator* ephemeral_input_tags, size_t n_ephemeral_input_tags, const secp256k1_generator* ephemeral_output_tag) {
293355
size_t rsizes[1]; /* array needed for borromean sig API */
294356
size_t i;
295357
size_t n_total_pubkeys;
296358
size_t n_used_pubkeys;
297-
secp256k1_gej ring_pubkeys[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
298-
secp256k1_scalar borromean_s[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
299-
secp256k1_ge inputs[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
300-
secp256k1_ge output;
359+
secp256k1_gej ring_pubkeys[SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS];
360+
secp256k1_scalar borromean_s[SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS];
301361
unsigned char msg32[32];
302362

303363
VERIFY_CHECK(ctx != NULL);
@@ -313,12 +373,12 @@ int secp256k1_surjectionproof_verify(const secp256k1_context* ctx, const secp256
313373
return 0;
314374
}
315375

316-
secp256k1_generator_load(&output, ephemeral_output_tag);
317-
for (i = 0; i < n_total_pubkeys; i++) {
318-
secp256k1_generator_load(&inputs[i], &ephemeral_input_tags[i]);
376+
/* Reject proofs with too many used inputs in USE_REDUCED_SURJECTION_PROOF_SIZE mode */
377+
if (n_used_pubkeys > SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS) {
378+
return 0;
319379
}
320380

321-
if (secp256k1_surjection_compute_public_keys(ring_pubkeys, n_used_pubkeys, inputs, n_total_pubkeys, proof->used_inputs, &output, 0, NULL) == 0) {
381+
if (secp256k1_surjection_compute_public_keys(ring_pubkeys, n_used_pubkeys, ephemeral_input_tags, n_total_pubkeys, proof->used_inputs, ephemeral_output_tag, 0, NULL) == 0) {
322382
return 0;
323383
}
324384

@@ -331,7 +391,7 @@ int secp256k1_surjectionproof_verify(const secp256k1_context* ctx, const secp256
331391
return 0;
332392
}
333393
}
334-
secp256k1_surjection_genmessage(msg32, inputs, n_total_pubkeys, &output);
394+
secp256k1_surjection_genmessage(msg32, ephemeral_input_tags, n_total_pubkeys, ephemeral_output_tag);
335395
return secp256k1_borromean_verify(&ctx->ecmult_ctx, NULL, &proof->data[0], borromean_s, ring_pubkeys, rsizes, 1, msg32, 32);
336396
}
337397

Diff for: secp256k1-zkp/src/modules/surjection/surjection_impl.h

+16-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#include "scalar.h"
1616
#include "hash.h"
1717

18-
SECP256K1_INLINE static void secp256k1_surjection_genmessage(unsigned char *msg32, secp256k1_ge *ephemeral_input_tags, size_t n_input_tags, secp256k1_ge *ephemeral_output_tag) {
18+
SECP256K1_INLINE static void secp256k1_surjection_genmessage(unsigned char *msg32, const secp256k1_generator *ephemeral_input_tags, size_t n_input_tags, const secp256k1_generator *ephemeral_output_tag) {
1919
/* compute message */
2020
size_t i;
2121
unsigned char pk_ser[33];
@@ -24,12 +24,12 @@ SECP256K1_INLINE static void secp256k1_surjection_genmessage(unsigned char *msg3
2424

2525
secp256k1_sha256_initialize(&sha256_en);
2626
for (i = 0; i < n_input_tags; i++) {
27-
secp256k1_eckey_pubkey_serialize(&ephemeral_input_tags[i], pk_ser, &pk_len, 1);
28-
assert(pk_len == sizeof(pk_ser));
27+
pk_ser[0] = 2 + (ephemeral_input_tags[i].data[63] & 1);
28+
memcpy(&pk_ser[1], &ephemeral_input_tags[i].data[0], 32);
2929
secp256k1_sha256_write(&sha256_en, pk_ser, pk_len);
3030
}
31-
secp256k1_eckey_pubkey_serialize(ephemeral_output_tag, pk_ser, &pk_len, 1);
32-
assert(pk_len == sizeof(pk_ser));
31+
pk_ser[0] = 2 + (ephemeral_output_tag->data[63] & 1);
32+
memcpy(&pk_ser[1], &ephemeral_output_tag->data[0], 32);
3333
secp256k1_sha256_write(&sha256_en, pk_ser, pk_len);
3434
secp256k1_sha256_finalize(&sha256_en, msg32);
3535
}
@@ -61,24 +61,29 @@ SECP256K1_INLINE static int secp256k1_surjection_genrand(secp256k1_scalar *s, si
6161
return 1;
6262
}
6363

64-
SECP256K1_INLINE static int secp256k1_surjection_compute_public_keys(secp256k1_gej *pubkeys, size_t n_pubkeys, const secp256k1_ge *input_tags, size_t n_input_tags, const unsigned char *used_tags, const secp256k1_ge *output_tag, size_t input_index, size_t *ring_input_index) {
64+
SECP256K1_INLINE static int secp256k1_surjection_compute_public_keys(secp256k1_gej *pubkeys, size_t n_pubkeys, const secp256k1_generator *input_tags, size_t n_input_tags, const unsigned char *used_tags, const secp256k1_generator *output_tag, size_t input_index, size_t *ring_input_index) {
6565
size_t i;
6666
size_t j = 0;
6767
for (i = 0; i < n_input_tags; i++) {
6868
if (used_tags[i / 8] & (1 << (i % 8))) {
6969
secp256k1_ge tmpge;
70-
secp256k1_ge_neg(&tmpge, &input_tags[i]);
70+
secp256k1_generator_load(&tmpge, &input_tags[i]);
71+
secp256k1_ge_neg(&tmpge, &tmpge);
72+
73+
VERIFY_CHECK(j < SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS);
74+
VERIFY_CHECK(j < n_pubkeys);
7175
secp256k1_gej_set_ge(&pubkeys[j], &tmpge);
72-
secp256k1_gej_add_ge_var(&pubkeys[j], &pubkeys[j], output_tag, NULL);
76+
77+
secp256k1_generator_load(&tmpge, output_tag);
78+
secp256k1_gej_add_ge_var(&pubkeys[j], &pubkeys[j], &tmpge, NULL);
7379
if (ring_input_index != NULL && input_index == i) {
7480
*ring_input_index = j;
7581
}
7682
j++;
77-
if (j > n_pubkeys) {
78-
return 0;
79-
}
8083
}
8184
}
85+
/* Caller needs to ensure that the number of set bits in used_tags (which we counted in j) equals n_pubkeys. */
86+
VERIFY_CHECK(j == n_pubkeys);
8287
return 1;
8388
}
8489

0 commit comments

Comments
 (0)