Use public multi-scalar mults in Trust Tokens where applicable.

The input points are all public, so we can use a faster multi-scalar
multiplication. This generalizes ec_point_mul_scalar_public to
ec_point_mul_scalar_public_batch. To support the batched DLEQ
construction, this function takes an arbirarily-length array of points
and allocates some temporaries if necessary.

First, to confirm that this doesn't affect the basic ECDSA verify case:
Before:
Did 6324 ECDSA P-384 verify operations in 3069342us (2060.4 ops/sec)
After:
Did 6324 ECDSA P-384 verify operations in 3063355us (2064.4 ops/sec) [+0.2%]

Results for Trust Tokens issue (Exp1) and finish_issuance (both):
Before:
Did 147 TrustToken-Exp0-Batch1 finish_issuance operations in 2059145us (71.4 ops/sec)
Did 14 TrustToken-Exp0-Batch10 finish_issuance operations in 2085888us (6.7 ops/sec)
Did 357 TrustToken-Exp1-Batch1 issue operations in 2068238us (172.6 ops/sec)
Did 286 TrustToken-Exp1-Batch1 finish_issuance operations in 2090932us (136.8 ops/sec)
Did 63 TrustToken-Exp1-Batch10 issue operations in 2068201us (30.5 ops/sec)
Did 56 TrustToken-Exp1-Batch10 finish_issuance operations in 2064796us (27.1 ops/sec)

After:
Did 168 TrustToken-Exp0-Batch1 finish_issuance operations in 2058891us (81.6 ops/sec) [+14.3%]
Did 16 TrustToken-Exp0-Batch10 finish_issuance operations in 2075742us (7.7 ops/sec) [+14.8%]
Did 378 TrustToken-Exp1-Batch1 issue operations in 2067956us (182.8 ops/sec) [+5.9%]
Did 336 TrustToken-Exp1-Batch1 finish_issuance operations in 2097757us (160.2 ops/sec) [+17.1%]
Did 105 TrustToken-Exp1-Batch10 issue operations in 2069934us (50.7 ops/sec) [+66.5%]
Did 88 TrustToken-Exp1-Batch10 finish_issuance operations in 2014621us (43.7 ops/sec) [+61.1%]

(This CL doesn't affect other operations.)

Change-Id: Ie643b06f44990ab52bf892a007732fde61cdffe5
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/41285
Reviewed-by: Steven Valdez <svaldez@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
This commit is contained in:
David Benjamin
2020-05-11 17:43:57 -04:00
committed by CQ bot account: commit-bot@chromium.org
parent b55a8c1580
commit a810d82575
5 changed files with 168 additions and 88 deletions
+17
View File
@@ -1006,10 +1006,27 @@ int ec_point_mul_scalar_public(const EC_GROUP *group, EC_RAW_POINT *r,
return 0;
}
if (group->meth->mul_public == NULL) {
return group->meth->mul_public_batch(group, r, g_scalar, p, p_scalar, 1);
}
group->meth->mul_public(group, r, g_scalar, p, p_scalar);
return 1;
}
int ec_point_mul_scalar_public_batch(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar,
const EC_RAW_POINT *points,
const EC_SCALAR *scalars, size_t num) {
if (group->meth->mul_public_batch == NULL) {
OPENSSL_PUT_ERROR(EC, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
}
return group->meth->mul_public_batch(group, r, g_scalar, points, scalars,
num);
}
int ec_point_mul_scalar(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_RAW_POINT *p, const EC_SCALAR *scalar) {
if (p == NULL || scalar == NULL) {
+1 -1
View File
@@ -508,7 +508,7 @@ DEFINE_METHOD_FUNCTION(EC_METHOD, EC_GFp_mont_method) {
out->mul = ec_GFp_mont_mul;
out->mul_base = ec_GFp_mont_mul_base;
out->mul_batch = ec_GFp_mont_mul_batch;
out->mul_public = ec_GFp_mont_mul_public;
out->mul_public_batch = ec_GFp_mont_mul_public_batch;
out->init_precomp = ec_GFp_mont_init_precomp;
out->mul_precomp = ec_GFp_mont_mul_precomp;
out->felem_mul = ec_GFp_mont_felem_mul;
+23 -3
View File
@@ -389,6 +389,19 @@ OPENSSL_EXPORT int ec_point_mul_scalar_public(const EC_GROUP *group,
const EC_RAW_POINT *p,
const EC_SCALAR *p_scalar);
// ec_point_mul_scalar_public_batch sets |r| to the sum of generator *
// |g_scalar| and |points[i]| * |scalars[i]| where |points| and |scalars| have
// |num| elements. It assumes that the inputs are public so there is no concern
// about leaking their values through timing. |g_scalar| may be NULL to skip
// that term.
//
// This function is not implemented for all curves. Add implementations as
// needed.
int ec_point_mul_scalar_public_batch(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar,
const EC_RAW_POINT *points,
const EC_SCALAR *scalars, size_t num);
// ec_point_select, in constant time, sets |out| to |a| if |mask| is all ones
// and |b| if |mask| is all zeros.
void ec_point_select(const EC_GROUP *group, EC_RAW_POINT *out, BN_ULONG mask,
@@ -483,9 +496,15 @@ struct ec_method_st {
// mul_public sets |r| to |g_scalar|*generator + |p_scalar|*|p|. It assumes
// that the inputs are public so there is no concern about leaking their
// values through timing.
//
// This function may be omitted if |mul_public_batch| is provided.
void (*mul_public)(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
const EC_SCALAR *p_scalar);
// mul_public_batch implements |ec_point_mul_scalar_public_batch|.
int (*mul_public_batch)(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar, const EC_RAW_POINT *points,
const EC_SCALAR *scalars, size_t num);
// init_precomp implements |ec_init_precomp|.
int (*init_precomp)(const EC_GROUP *group, EC_PRECOMP *out,
@@ -632,9 +651,10 @@ void ec_GFp_mont_mul_precomp(const EC_GROUP *group, EC_RAW_POINT *r,
void ec_compute_wNAF(const EC_GROUP *group, int8_t *out,
const EC_SCALAR *scalar, size_t bits, int w);
void ec_GFp_mont_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
const EC_SCALAR *p_scalar);
int ec_GFp_mont_mul_public_batch(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar,
const EC_RAW_POINT *points,
const EC_SCALAR *scalars, size_t num);
// method functions in simple.c
int ec_GFp_simple_group_init(EC_GROUP *);
+60 -18
View File
@@ -174,24 +174,57 @@ static void lookup_precomp(const EC_GROUP *group, EC_RAW_POINT *out,
// EC_WNAF_TABLE_SIZE is the table size to use for |ec_GFp_mont_mul_public|.
#define EC_WNAF_TABLE_SIZE (1 << (EC_WNAF_WINDOW_BITS - 1))
void ec_GFp_mont_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
const EC_SCALAR *p_scalar) {
// EC_WNAF_STACK is the number of points worth of data to stack-allocate and
// avoid a malloc.
#define EC_WNAF_STACK 3
int ec_GFp_mont_mul_public_batch(const EC_GROUP *group, EC_RAW_POINT *r,
const EC_SCALAR *g_scalar,
const EC_RAW_POINT *points,
const EC_SCALAR *scalars, size_t num) {
size_t bits = BN_num_bits(&group->order);
size_t wNAF_len = bits + 1;
int ret = 0;
int8_t wNAF_stack[EC_WNAF_STACK][EC_MAX_BYTES * 8 + 1];
int8_t (*wNAF_alloc)[EC_MAX_BYTES * 8 + 1] = NULL;
int8_t (*wNAF)[EC_MAX_BYTES * 8 + 1];
EC_RAW_POINT precomp_stack[EC_WNAF_STACK][EC_WNAF_TABLE_SIZE];
EC_RAW_POINT (*precomp_alloc)[EC_WNAF_TABLE_SIZE] = NULL;
EC_RAW_POINT (*precomp)[EC_WNAF_TABLE_SIZE];
if (num <= EC_WNAF_STACK) {
wNAF = wNAF_stack;
precomp = precomp_stack;
} else {
if (num >= ((size_t)-1) / sizeof(wNAF_alloc[0]) ||
num >= ((size_t)-1) / sizeof(precomp_alloc[0])) {
OPENSSL_PUT_ERROR(EC, ERR_R_OVERFLOW);
goto err;
}
wNAF_alloc = OPENSSL_malloc(num * sizeof(wNAF_alloc[0]));
precomp_alloc = OPENSSL_malloc(num * sizeof(precomp_alloc[0]));
if (wNAF_alloc == NULL || precomp_alloc == NULL) {
OPENSSL_PUT_ERROR(EC, ERR_R_MALLOC_FAILURE);
goto err;
}
wNAF = wNAF_alloc;
precomp = precomp_alloc;
}
int8_t g_wNAF[EC_MAX_BYTES * 8 + 1];
EC_RAW_POINT g_precomp[EC_WNAF_TABLE_SIZE];
assert(wNAF_len <= OPENSSL_ARRAY_SIZE(g_wNAF));
const EC_RAW_POINT *g = &group->generator->raw;
ec_compute_wNAF(group, g_wNAF, g_scalar, bits, EC_WNAF_WINDOW_BITS);
compute_precomp(group, g_precomp, g, EC_WNAF_TABLE_SIZE);
if (g_scalar != NULL) {
ec_compute_wNAF(group, g_wNAF, g_scalar, bits, EC_WNAF_WINDOW_BITS);
compute_precomp(group, g_precomp, g, EC_WNAF_TABLE_SIZE);
}
int8_t p_wNAF[EC_MAX_BYTES * 8 + 1];
EC_RAW_POINT p_precomp[EC_WNAF_TABLE_SIZE];
assert(wNAF_len <= OPENSSL_ARRAY_SIZE(p_wNAF));
ec_compute_wNAF(group, p_wNAF, p_scalar, bits, EC_WNAF_WINDOW_BITS);
compute_precomp(group, p_precomp, p, EC_WNAF_TABLE_SIZE);
for (size_t i = 0; i < num; i++) {
assert(wNAF_len <= OPENSSL_ARRAY_SIZE(wNAF[i]));
ec_compute_wNAF(group, wNAF[i], &scalars[i], bits, EC_WNAF_WINDOW_BITS);
compute_precomp(group, precomp[i], &points[i], EC_WNAF_TABLE_SIZE);
}
EC_RAW_POINT tmp;
int r_is_at_infinity = 1;
@@ -200,7 +233,7 @@ void ec_GFp_mont_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
ec_GFp_mont_dbl(group, r, r);
}
if (g_wNAF[k] != 0) {
if (g_scalar != NULL && g_wNAF[k] != 0) {
lookup_precomp(group, &tmp, g_precomp, g_wNAF[k]);
if (r_is_at_infinity) {
ec_GFp_simple_point_copy(r, &tmp);
@@ -210,13 +243,15 @@ void ec_GFp_mont_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
}
}
if (p_wNAF[k] != 0) {
lookup_precomp(group, &tmp, p_precomp, p_wNAF[k]);
if (r_is_at_infinity) {
ec_GFp_simple_point_copy(r, &tmp);
r_is_at_infinity = 0;
} else {
ec_GFp_mont_add(group, r, r, &tmp);
for (size_t i = 0; i < num; i++) {
if (wNAF[i][k] != 0) {
lookup_precomp(group, &tmp, precomp[i], wNAF[i][k]);
if (r_is_at_infinity) {
ec_GFp_simple_point_copy(r, &tmp);
r_is_at_infinity = 0;
} else {
ec_GFp_mont_add(group, r, r, &tmp);
}
}
}
}
@@ -224,4 +259,11 @@ void ec_GFp_mont_mul_public(const EC_GROUP *group, EC_RAW_POINT *r,
if (r_is_at_infinity) {
ec_GFp_simple_point_set_to_infinity(group, r);
}
ret = 1;
err:
OPENSSL_free(wNAF_alloc);
OPENSSL_free(precomp_alloc);
return ret;
}
+67 -66
View File
@@ -128,6 +128,16 @@ static int cbs_get_prefixed_point(CBS *cbs, const EC_GROUP *group,
return 1;
}
static int mul_public_3(const EC_GROUP *group, EC_RAW_POINT *out,
const EC_RAW_POINT *p0, const EC_SCALAR *scalar0,
const EC_RAW_POINT *p1, const EC_SCALAR *scalar1,
const EC_RAW_POINT *p2, const EC_SCALAR *scalar2) {
EC_RAW_POINT points[3] = {*p0, *p1, *p2};
EC_SCALAR scalars[3] = {*scalar0, *scalar1, *scalar2};
return ec_point_mul_scalar_public_batch(group, out, /*g_scalar=*/NULL, points,
scalars, 3);
}
void PMBTOKEN_PRETOKEN_free(PMBTOKEN_PRETOKEN *pretoken) {
OPENSSL_free(pretoken);
}
@@ -594,7 +604,9 @@ static int dleq_verify(const PMBTOKEN_METHOD *method, CBS *cbs,
// We verify a DLEQ proof for the validity token and a DLEQOR2 proof for the
// private metadata token. To allow amortizing Jacobian-to-affine conversions,
// we compute Ki for both proofs first.
// we compute Ki for both proofs first. Additionally, all inputs to this
// function are public, so we can use the faster variable-time
// multiplications.
enum {
idx_T,
idx_S,
@@ -620,17 +632,14 @@ static int dleq_verify(const PMBTOKEN_METHOD *method, CBS *cbs,
}
// Ks = us*(G;T) + vs*(H;S) - cs*(pubs;Ws)
//
// TODO(davidben): The multiplications in this function are public and can be
// switched to a public batch multiplication function if we add one.
EC_RAW_POINT pubs;
ec_affine_to_jacobian(group, &pubs, &pub->pubs);
EC_SCALAR minus_cs;
ec_scalar_neg(group, &minus_cs, &cs);
if (!ec_point_mul_scalar_batch(group, &jacobians[idx_Ks0], g, &us, &method->h,
&vs, &pubs, &minus_cs) ||
!ec_point_mul_scalar_batch(group, &jacobians[idx_Ks1], T, &us, S, &vs, Ws,
&minus_cs)) {
if (!mul_public_3(group, &jacobians[idx_Ks0], g, &us, &method->h, &vs, &pubs,
&minus_cs) ||
!mul_public_3(group, &jacobians[idx_Ks1], T, &us, S, &vs, Ws,
&minus_cs)) {
return 0;
}
@@ -653,15 +662,13 @@ static int dleq_verify(const PMBTOKEN_METHOD *method, CBS *cbs,
ec_scalar_neg(group, &minus_c0, &c0);
ec_scalar_neg(group, &minus_c1, &c1);
if (// K0 = u0*(G;T) + v0*(H;S) - c0*(pub0;W)
!ec_point_mul_scalar_batch(group, &jacobians[idx_K00], g, &u0, &method->h,
&v0, &pub0, &minus_c0) ||
!ec_point_mul_scalar_batch(group, &jacobians[idx_K01], T, &u0, S, &v0, W,
&minus_c0) ||
!mul_public_3(group, &jacobians[idx_K00], g, &u0, &method->h, &v0, &pub0,
&minus_c0) ||
!mul_public_3(group, &jacobians[idx_K01], T, &u0, S, &v0, W, &minus_c0) ||
// K1 = u1*(G;T) + v1*(H;S) - c1*(pub1;W)
!ec_point_mul_scalar_batch(group, &jacobians[idx_K10], g, &u1, &method->h,
&v1, &pub1, &minus_c1) ||
!ec_point_mul_scalar_batch(group, &jacobians[idx_K11], T, &u1, S, &v1, W,
&minus_c1)) {
!mul_public_3(group, &jacobians[idx_K10], g, &u1, &method->h, &v1, &pub1,
&minus_c1) ||
!mul_public_3(group, &jacobians[idx_K11], T, &u1, S, &v1, W, &minus_c1)) {
return 0;
}
@@ -722,10 +729,12 @@ static int pmbtoken_sign(const PMBTOKEN_METHOD *method,
EC_RAW_POINT *Sps = NULL;
EC_RAW_POINT *Wps = NULL;
EC_RAW_POINT *Wsps = NULL;
EC_SCALAR *es = NULL;
CBB batch_cbb;
CBB_zero(&batch_cbb);
if (method->batched_proof) {
if (num_to_issue > ((size_t)-1) / sizeof(EC_RAW_POINT)) {
if (num_to_issue > ((size_t)-1) / sizeof(EC_RAW_POINT) ||
num_to_issue > ((size_t)-1) / sizeof(EC_SCALAR)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
goto err;
}
@@ -733,10 +742,12 @@ static int pmbtoken_sign(const PMBTOKEN_METHOD *method,
Sps = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
Wps = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
Wsps = OPENSSL_malloc(num_to_issue * sizeof(EC_RAW_POINT));
es = OPENSSL_malloc(num_to_issue * sizeof(EC_SCALAR));
if (!Tps ||
!Sps ||
!Wps ||
!Wsps ||
!es ||
!CBB_init(&batch_cbb, 0) ||
!point_to_cbb(&batch_cbb, method->group, &key->pubs) ||
!point_to_cbb(&batch_cbb, method->group, &key->pub0) ||
@@ -808,35 +819,29 @@ static int pmbtoken_sign(const PMBTOKEN_METHOD *method,
}
// The DLEQ batching construction is described in appendix B of
// https://eprint.iacr.org/2020/072/20200324:214215.
// https://eprint.iacr.org/2020/072/20200324:214215. Note the additional
// computations all act on public inputs.
if (method->batched_proof) {
EC_RAW_POINT Tp_batch, Sp_batch, Wp_batch, Wsp_batch;
for (size_t i = 0; i < num_to_issue; i++) {
EC_SCALAR e;
if (!hash_c_batch(method, &e, &batch_cbb, i)) {
if (!hash_c_batch(method, &es[i], &batch_cbb, i)) {
goto err;
}
}
EC_RAW_POINT Tp_e, Sp_e, Wp_e, Wsp_e;
if (!ec_point_mul_scalar(group, &Tp_e, &Tps[i], &e) ||
!ec_point_mul_scalar(group, &Sp_e, &Sps[i], &e) ||
!ec_point_mul_scalar(group, &Wp_e, &Wps[i], &e) ||
!ec_point_mul_scalar(group, &Wsp_e, &Wsps[i], &e)) {
goto err;
}
// TODO: Switch this to a multi-scalar multiplication.
if (i == 0) {
Tp_batch = Tp_e;
Sp_batch = Sp_e;
Wp_batch = Wp_e;
Wsp_batch = Wsp_e;
} else {
group->meth->add(group, &Tp_batch, &Tp_batch, &Tp_e);
group->meth->add(group, &Sp_batch, &Sp_batch, &Sp_e);
group->meth->add(group, &Wp_batch, &Wp_batch, &Wp_e);
group->meth->add(group, &Wsp_batch, &Wsp_batch, &Wsp_e);
}
EC_RAW_POINT Tp_batch, Sp_batch, Wp_batch, Wsp_batch;
if (!ec_point_mul_scalar_public_batch(group, &Tp_batch,
/*g_scalar=*/NULL, Tps, es,
num_to_issue) ||
!ec_point_mul_scalar_public_batch(group, &Sp_batch,
/*g_scalar=*/NULL, Sps, es,
num_to_issue) ||
!ec_point_mul_scalar_public_batch(group, &Wp_batch,
/*g_scalar=*/NULL, Wps, es,
num_to_issue) ||
!ec_point_mul_scalar_public_batch(group, &Wsp_batch,
/*g_scalar=*/NULL, Wsps, es,
num_to_issue)) {
goto err;
}
CBB proof;
@@ -862,6 +867,7 @@ err:
OPENSSL_free(Sps);
OPENSSL_free(Wps);
OPENSSL_free(Wsps);
OPENSSL_free(es);
CBB_cleanup(&batch_cbb);
return ret;
}
@@ -888,10 +894,12 @@ static STACK_OF(TRUST_TOKEN) *
EC_RAW_POINT *Sps = NULL;
EC_RAW_POINT *Wps = NULL;
EC_RAW_POINT *Wsps = NULL;
EC_SCALAR *es = NULL;
CBB batch_cbb;
CBB_zero(&batch_cbb);
if (method->batched_proof) {
if (count > ((size_t)-1) / sizeof(EC_RAW_POINT)) {
if (count > ((size_t)-1) / sizeof(EC_RAW_POINT) ||
count > ((size_t)-1) / sizeof(EC_SCALAR)) {
OPENSSL_PUT_ERROR(TRUST_TOKEN, ERR_R_OVERFLOW);
goto err;
}
@@ -899,10 +907,12 @@ static STACK_OF(TRUST_TOKEN) *
Sps = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
Wps = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
Wsps = OPENSSL_malloc(count * sizeof(EC_RAW_POINT));
es = OPENSSL_malloc(count * sizeof(EC_SCALAR));
if (!Tps ||
!Sps ||
!Wps ||
!Wsps ||
!es ||
!CBB_init(&batch_cbb, 0) ||
!point_to_cbb(&batch_cbb, method->group, &key->pubs) ||
!point_to_cbb(&batch_cbb, method->group, &key->pub0) ||
@@ -1006,35 +1016,25 @@ static STACK_OF(TRUST_TOKEN) *
}
// The DLEQ batching construction is described in appendix B of
// https://eprint.iacr.org/2020/072/20200324:214215.
// https://eprint.iacr.org/2020/072/20200324:214215. Note the additional
// computations all act on public inputs.
if (method->batched_proof) {
EC_RAW_POINT Tp_batch, Sp_batch, Wp_batch, Wsp_batch;
for (size_t i = 0; i < count; i++) {
EC_SCALAR e;
if (!hash_c_batch(method, &e, &batch_cbb, i)) {
if (!hash_c_batch(method, &es[i], &batch_cbb, i)) {
goto err;
}
}
EC_RAW_POINT Tp_e, Sp_e, Wp_e, Wsp_e;
if (!ec_point_mul_scalar(group, &Tp_e, &Tps[i], &e) ||
!ec_point_mul_scalar(group, &Sp_e, &Sps[i], &e) ||
!ec_point_mul_scalar(group, &Wp_e, &Wps[i], &e) ||
!ec_point_mul_scalar(group, &Wsp_e, &Wsps[i], &e)) {
goto err;
}
// TODO: Switch this to a multi-scalar multiplication.
if (i == 0) {
Tp_batch = Tp_e;
Sp_batch = Sp_e;
Wp_batch = Wp_e;
Wsp_batch = Wsp_e;
} else {
group->meth->add(group, &Tp_batch, &Tp_batch, &Tp_e);
group->meth->add(group, &Sp_batch, &Sp_batch, &Sp_e);
group->meth->add(group, &Wp_batch, &Wp_batch, &Wp_e);
group->meth->add(group, &Wsp_batch, &Wsp_batch, &Wsp_e);
}
EC_RAW_POINT Tp_batch, Sp_batch, Wp_batch, Wsp_batch;
if (!ec_point_mul_scalar_public_batch(group, &Tp_batch,
/*g_scalar=*/NULL, Tps, es, count) ||
!ec_point_mul_scalar_public_batch(group, &Sp_batch,
/*g_scalar=*/NULL, Sps, es, count) ||
!ec_point_mul_scalar_public_batch(group, &Wp_batch,
/*g_scalar=*/NULL, Wps, es, count) ||
!ec_point_mul_scalar_public_batch(group, &Wsp_batch,
/*g_scalar=*/NULL, Wsps, es, count)) {
goto err;
}
CBS proof;
@@ -1053,6 +1053,7 @@ err:
OPENSSL_free(Sps);
OPENSSL_free(Wps);
OPENSSL_free(Wsps);
OPENSSL_free(es);
CBB_cleanup(&batch_cbb);
if (!ok) {
sk_TRUST_TOKEN_pop_free(ret, TRUST_TOKEN_free);