Skip to content

Commit 6ef405d

Browse files
committed
key: don't allocate secure mem for null (invalid) key
Instead of storing the key material as an std::vector (with secure allocator), use a secure_unique_ptr to a 32-byte array, and use nullptr for invalid keys. This means a smaller CKey type, and no secure/dynamic memory usage for invalid keys.
1 parent d9841a7 commit 6ef405d

File tree

2 files changed

+59
-38
lines changed

2 files changed

+59
-38
lines changed

src/key.cpp

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,21 @@ bool CKey::Check(const unsigned char *vch) {
159159
}
160160

161161
void CKey::MakeNewKey(bool fCompressedIn) {
162+
MakeKeyData();
162163
do {
163-
GetStrongRandBytes(keydata);
164-
} while (!Check(keydata.data()));
165-
fValid = true;
164+
GetStrongRandBytes(*keydata);
165+
} while (!Check(keydata->data()));
166166
fCompressed = fCompressedIn;
167167
}
168168

169169
bool CKey::Negate()
170170
{
171-
assert(fValid);
172-
return secp256k1_ec_seckey_negate(secp256k1_context_sign, keydata.data());
171+
assert(keydata);
172+
return secp256k1_ec_seckey_negate(secp256k1_context_sign, keydata->data());
173173
}
174174

175175
CPrivKey CKey::GetPrivKey() const {
176-
assert(fValid);
176+
assert(keydata);
177177
CPrivKey seckey;
178178
int ret;
179179
size_t seckeylen;
@@ -186,7 +186,7 @@ CPrivKey CKey::GetPrivKey() const {
186186
}
187187

188188
CPubKey CKey::GetPubKey() const {
189-
assert(fValid);
189+
assert(keydata);
190190
secp256k1_pubkey pubkey;
191191
size_t clen = CPubKey::SIZE;
192192
CPubKey result;
@@ -212,7 +212,7 @@ bool SigHasLowR(const secp256k1_ecdsa_signature* sig)
212212
}
213213

214214
bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& vchSig, bool grind, uint32_t test_case) const {
215-
if (!fValid)
215+
if (!keydata)
216216
return false;
217217
vchSig.resize(CPubKey::SIGNATURE_SIZE);
218218
size_t nSigLen = CPubKey::SIGNATURE_SIZE;
@@ -253,7 +253,7 @@ bool CKey::VerifyPubKey(const CPubKey& pubkey) const {
253253
}
254254

255255
bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig) const {
256-
if (!fValid)
256+
if (!keydata)
257257
return false;
258258
vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE);
259259
int rec = -1;
@@ -301,10 +301,12 @@ bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint2
301301
}
302302

303303
bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) {
304-
if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size()))
304+
MakeKeyData();
305+
if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size())) {
306+
ClearKeyData();
305307
return false;
308+
}
306309
fCompressed = vchPubKey.IsCompressed();
307-
fValid = true;
308310

309311
if (fSkipCheck)
310312
return true;
@@ -325,22 +327,21 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
325327
BIP32Hash(cc, nChild, 0, begin(), vout.data());
326328
}
327329
memcpy(ccChild.begin(), vout.data()+32, 32);
328-
memcpy((unsigned char*)keyChild.begin(), begin(), 32);
330+
keyChild.Set(begin(), begin() + 32, true);
329331
bool ret = secp256k1_ec_seckey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), vout.data());
330-
keyChild.fCompressed = true;
331-
keyChild.fValid = ret;
332+
if (!ret) keyChild.ClearKeyData();
332333
return ret;
333334
}
334335

335336
EllSwiftPubKey CKey::EllSwiftCreate(Span<const std::byte> ent32) const
336337
{
337-
assert(fValid);
338+
assert(keydata);
338339
assert(ent32.size() == 32);
339340
std::array<std::byte, EllSwiftPubKey::size()> encoded_pubkey;
340341

341342
auto success = secp256k1_ellswift_create(secp256k1_context_sign,
342343
UCharCast(encoded_pubkey.data()),
343-
keydata.data(),
344+
keydata->data(),
344345
UCharCast(ent32.data()));
345346

346347
// Should always succeed for valid keys (asserted above).
@@ -350,7 +351,7 @@ EllSwiftPubKey CKey::EllSwiftCreate(Span<const std::byte> ent32) const
350351

351352
ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, const EllSwiftPubKey& our_ellswift, bool initiating) const
352353
{
353-
assert(fValid);
354+
assert(keydata);
354355

355356
ECDHSecret output;
356357
// BIP324 uses the initiator as party A, and the responder as party B. Remap the inputs
@@ -359,7 +360,7 @@ ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, c
359360
UCharCast(output.data()),
360361
UCharCast(initiating ? our_ellswift.data() : their_ellswift.data()),
361362
UCharCast(initiating ? their_ellswift.data() : our_ellswift.data()),
362-
keydata.data(),
363+
keydata->data(),
363364
initiating ? 0 : 1,
364365
secp256k1_ellswift_xdh_hash_function_bip324,
365366
nullptr);

src/key.h

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,57 +46,77 @@ class CKey
4646
"COMPRESSED_SIZE is larger than SIZE");
4747

4848
private:
49-
//! Whether this private key is valid. We check for correctness when modifying the key
50-
//! data, so fValid should always correspond to the actual state.
51-
bool fValid{false};
49+
/** Internal data container for private key material. */
50+
using KeyType = std::array<unsigned char, 32>;
5251

5352
//! Whether the public key corresponding to this private key is (to be) compressed.
5453
bool fCompressed{false};
5554

56-
//! The actual byte data
57-
std::vector<unsigned char, secure_allocator<unsigned char> > keydata;
55+
//! The actual byte data. nullptr for invalid keys.
56+
secure_unique_ptr<KeyType> keydata;
5857

5958
//! Check whether the 32-byte array pointed to by vch is valid keydata.
6059
bool static Check(const unsigned char* vch);
6160

61+
void MakeKeyData()
62+
{
63+
if (!keydata) keydata = make_secure_unique<KeyType>();
64+
}
65+
66+
void ClearKeyData()
67+
{
68+
keydata.reset();
69+
}
70+
6271
public:
63-
//! Construct an invalid private key.
64-
CKey()
72+
CKey() noexcept = default;
73+
CKey(CKey&&) noexcept = default;
74+
CKey& operator=(CKey&&) noexcept = default;
75+
76+
CKey& operator=(const CKey& other)
6577
{
66-
// Important: vch must be 32 bytes in length to not break serialization
67-
keydata.resize(32);
78+
if (other.keydata) {
79+
MakeKeyData();
80+
*keydata = *other.keydata;
81+
} else {
82+
ClearKeyData();
83+
}
84+
fCompressed = other.fCompressed;
85+
return *this;
6886
}
6987

88+
CKey(const CKey& other) { *this = other; }
89+
7090
friend bool operator==(const CKey& a, const CKey& b)
7191
{
7292
return a.fCompressed == b.fCompressed &&
7393
a.size() == b.size() &&
74-
memcmp(a.keydata.data(), b.keydata.data(), a.size()) == 0;
94+
memcmp(a.data(), b.data(), a.size()) == 0;
7595
}
7696

7797
//! Initialize using begin and end iterators to byte data.
7898
template <typename T>
7999
void Set(const T pbegin, const T pend, bool fCompressedIn)
80100
{
81-
if (size_t(pend - pbegin) != keydata.size()) {
82-
fValid = false;
101+
if (size_t(pend - pbegin) != std::tuple_size_v<KeyType>) {
102+
ClearKeyData();
83103
} else if (Check(&pbegin[0])) {
84-
memcpy(keydata.data(), (unsigned char*)&pbegin[0], keydata.size());
85-
fValid = true;
104+
MakeKeyData();
105+
memcpy(keydata->data(), (unsigned char*)&pbegin[0], keydata->size());
86106
fCompressed = fCompressedIn;
87107
} else {
88-
fValid = false;
108+
ClearKeyData();
89109
}
90110
}
91111

92112
//! Simple read-only vector-like interface.
93-
unsigned int size() const { return (fValid ? keydata.size() : 0); }
94-
const std::byte* data() const { return reinterpret_cast<const std::byte*>(keydata.data()); }
95-
const unsigned char* begin() const { return keydata.data(); }
96-
const unsigned char* end() const { return keydata.data() + size(); }
113+
unsigned int size() const { return keydata ? keydata->size() : 0; }
114+
const std::byte* data() const { return keydata ? reinterpret_cast<const std::byte*>(keydata->data()) : nullptr; }
115+
const unsigned char* begin() const { return keydata ? keydata->data() : nullptr; }
116+
const unsigned char* end() const { return begin() + size(); }
97117

98118
//! Check whether this private key is valid.
99-
bool IsValid() const { return fValid; }
119+
bool IsValid() const { return !!keydata; }
100120

101121
//! Check whether the public key corresponding to this private key is (to be) compressed.
102122
bool IsCompressed() const { return fCompressed; }

0 commit comments

Comments
 (0)