1

I am using a openssl EVP_PKEY_sign where the signature buffer size is already known and can be allocated using a vector.

size_t SignatureLength;
std::vector<unsigned char> Signature;
EVP_PKEY_sign(EvpPkeyCtx, NULL, &SignatureLength, MessageDigest.data(), MessageDigest.size());
Signature.resize(SignatureLength);
EVP_PKEY_sign(EvpPkeyCtx, Signature.data(), &SignatureLength, MessageDigest.data(), MessageDigest.size());
Signature.resize(SignatureLength);

I am using the Signature.data() to get the raw pointer to the buffer used by the vector. The first call to EVP_PKEY_sign gives the maximum length of the output signature buffer. On calling resize the buffer is filled-up with 0's which cause an additional overhead making it an O(n) operation where only O(1) allocation is needed.

Alternative is to call reserve but it fails as it only allocates memory and post calling EVP_PKEY_sign again the MessageDigest.size() would still be zero and resizing it to actual signature length(from the second call) then would overwrite the buffer with default values.

What is the efficient way to do this?

FatSnake
  • 51
  • 3
  • 10
  • The fundamental problem here is interop between two different languages, C and C++. It's comparatively easy - C++ and Python is harder, for instance, or C and Java. But even so, there's a small efficiency penalty because C and C++ have different ways to manage memory. – MSalters Jan 19 '21 at 14:47

2 Answers2

1

I agree with the other answer that the value initialization does now matter. If you want to get around it nonetheless, you could use std::unique_ptr<unsigned char[]> instead:

size_t SignatureLength;

EVP_PKEY_sign(EvpPkeyCtx, NULL, &SignatureLength, MessageDigest.data(), MessageDigest.size());
std::unique_ptr<unsigned char[]> Signature{new char[SignatureLength]};
EVP_PKEY_sign(EvpPkeyCtx, *Signature, &SignatureLength, MessageDigest.data(), MessageDigest.size());

I think that many times, an std::unique_ptr<T[]> works quite nicely with C-APIs that would in C be used with some manually managed array or stack buffer. The above code has two small caveats:

  1. You have a raw new in there. That's a bit ugly, but no problem, since constructing an std::unique_ptr from a raw pointer cannot throw and you are not calling any potentially throwing constructors with the new so this is will never leak. If you use make_unique, it will value initialize the array, same as the vector.
  2. The unique_ptr does not know it's own size, so you have to remember that for yourself or write a wrapper around that. This also implies that you cannot resize the memory underneath it to the actual length. But note, that the resize for vector also just reduces the size of the vector (basically writes to the size member variabel) and does not free any capacity (the second would require a new allocation and a copy of the whole data, which can be pretty expensive).
n314159
  • 4,990
  • 1
  • 5
  • 20
0

Signatures tend to be short. Setting a low number of values to 0 is an extremely fast operation and thus not likely to be a significant cost in relation to the single allocation for example and especially not in relation to the calculation of the signature which has linear complexity at best.

It is possible to avoid the initialisation using a custom allocator.


You could simplify by using the constructor to allocate the memory upon creating the vector:

EVP_PKEY_sign(EvpPkeyCtx, nullptr, &SignatureLength, ...
std::vector<unsigned char> Signature(SignatureLength);
EVP_PKEY_sign(EvpPkeyCtx, Signature.data(), ...

This doesn's affect the initialisation though.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    DefaultInsertable is value initialization, isn't it? And value initialization of a non-class non-array type sets it to zero. Besides, if it wasn't, the C++11 change would break millions of perfectly sane C++98 programs. – MSalters Jan 19 '21 at 14:42
  • @MSalters Hmm, it appears so. I've amended the answer. – eerorika Jan 19 '21 at 15:00